sc/inc/document.hxx                    |    3 
 sc/inc/queryevaluator.hxx              |   17 ++---
 sc/inc/queryiter.hxx                   |    6 -
 sc/inc/rangecache.hxx                  |   24 +++++--
 sc/source/core/data/documen2.cxx       |    7 +-
 sc/source/core/data/queryevaluator.cxx |   36 ++++++----
 sc/source/core/data/queryiter.cxx      |   34 +++++-----
 sc/source/core/tool/interpr1.cxx       |    6 -
 sc/source/core/tool/rangecache.cxx     |  112 +++++++++++++++++++++++++++++----
 9 files changed, 179 insertions(+), 66 deletions(-)

New commits:
commit 5b189abc13d4a9e408c82298e4ede0fdf505002d
Author:     Luboš Luňák <l.lu...@collabora.com>
AuthorDate: Mon May 9 12:26:57 2022 +0200
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Wed May 11 11:49:24 2022 +0200

    make ScSortedRangeCache work for also ScQueryEntry::ByString
    
    Change-Id: Ifa769e20d91f7899fa81df537a7f3b7aeff52115
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134125
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <l.lu...@collabora.com>

diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 2f79825979a9..f61650e5a256 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -1399,7 +1399,8 @@ public:
                     /** Creates a ScLookupCache cache for the range if it
                         doesn't already exist. */
     ScLookupCache & GetLookupCache( const ScRange & rRange, 
ScInterpreterContext* pContext );
-    ScSortedRangeCache & GetSortedRangeCache( const ScRange & rRange, bool 
bDescending, ScInterpreterContext* pContext );
+    ScSortedRangeCache & GetSortedRangeCache( const ScRange & rRange, const 
ScQueryParam& param,
+                                              ScInterpreterContext* pContext );
                     /** Only ScLookupCache dtor uses RemoveLookupCache(), do
                         not use elsewhere! */
     void            RemoveLookupCache( ScLookupCache & rCache );
diff --git a/sc/inc/queryevaluator.hxx b/sc/inc/queryevaluator.hxx
index a1fd20111071..13e3f6ae93c8 100644
--- a/sc/inc/queryevaluator.hxx
+++ b/sc/inc/queryevaluator.hxx
@@ -74,16 +74,14 @@ class ScQueryEvaluator
 
     static bool isPartialTextMatchOp(const ScQueryEntry& rEntry);
     static bool isTextMatchOp(const ScQueryEntry& rEntry);
+    static bool isMatchWholeCellHelper(bool docMatchWholeCell, const 
ScQueryEntry& rEntry);
+    bool isMatchWholeCell(const ScQueryEntry& rEntry) const;
     void setupTransliteratorIfNeeded();
     void setupCollatorIfNeeded();
 
     bool isRealWildOrRegExp(const ScQueryEntry& rEntry) const;
     bool isTestWildOrRegExp(const ScQueryEntry& rEntry) const;
-    static bool isQueryByValue(const ScQueryEntry& rEntry, 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);
 
@@ -91,9 +89,6 @@ class ScQueryEvaluator
                                          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>
@@ -118,6 +113,14 @@ public:
 
     bool ValidQuery(SCROW nRow, const ScRefCellValue* pCell = nullptr,
                     sc::TableColumnBlockPositionSet* pBlockPos = nullptr);
+
+    static bool isQueryByValue(const ScQueryEntry& rEntry, const 
ScQueryEntry::Item& rItem,
+                               const ScRefCellValue& rCell);
+    static bool isQueryByString(const ScQueryEntry& rEntry, const 
ScQueryEntry::Item& rItem,
+                                const ScRefCellValue& rCell);
+    OUString getCellString(const ScRefCellValue& rCell, SCROW nRow, const 
ScQueryEntry& rEntry,
+                           const svl::SharedString** sharedString);
+    static bool isMatchWholeCell(const ScDocument& rDoc, const ScQueryEntry& 
rEntry);
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/queryiter.hxx b/sc/inc/queryiter.hxx
index c27aadf92c68..545955d77963 100644
--- a/sc/inc/queryiter.hxx
+++ b/sc/inc/queryiter.hxx
@@ -188,7 +188,7 @@ protected:
        or if the range is not properly sorted, with nRow in that case set to 
the first row or after
        the last row. In that case use GetFirst().
     */
-    bool BinarySearch( SCCOL col );
+    bool BinarySearch( SCCOL col, bool forEqual = false );
 
                     /** If set, iterator stops on first non-matching cell
                         content. May be used in SC_LESS_EQUAL queries where a
@@ -318,7 +318,7 @@ public:
         SCTAB nTable, const ScQueryParam& aParam, bool bMod)
     : Base( rDocument, rContext, nTable, aParam, bMod ) {}
     // Returns true if this iterator can be used for the given query.
-    static bool CanBeUsed(const ScQueryParam& aParam);
+    static bool CanBeUsed(const ScDocument& rDoc, const ScQueryParam& aParam);
 };
 
 
@@ -366,7 +366,7 @@ public:
         SCTAB nTable, const ScQueryParam& aParam, bool bMod)
     : Base( rDocument, rContext, nTable, aParam, bMod ) {}
     // Returns true if this iterator can be used for the given query.
-    static bool CanBeUsed(const ScQueryParam& aParam);
+    static bool CanBeUsed(const ScDocument& rDoc, const ScQueryParam& aParam);
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/rangecache.hxx b/sc/inc/rangecache.hxx
index 92e9ae69a766..0db3536b71ff 100644
--- a/sc/inc/rangecache.hxx
+++ b/sc/inc/rangecache.hxx
@@ -20,12 +20,15 @@
 #pragma once
 
 #include "address.hxx"
+#include <o3tl/hash_combine.hxx>
 #include <svl/listener.hxx>
 
 #include <memory>
 #include <unordered_map>
 
 class ScDocument;
+struct ScInterpreterContext;
+struct ScQueryParam;
 struct ScSortedRangeCacheMap;
 
 /** Sorted cache for one range used with interpreter functions such as VLOOKUP
@@ -41,8 +44,8 @@ class ScSortedRangeCache final : public SvtListener
 {
 public:
     /// MUST be new'd because Notify() deletes.
-    ScSortedRangeCache(ScDocument* pDoc, const ScRange& rRange, bool 
bDescending,
-                       ScSortedRangeCacheMap& cacheMap);
+    ScSortedRangeCache(ScDocument* pDoc, const ScRange& rRange, const 
ScQueryParam& param,
+                       ScSortedRangeCacheMap& cacheMap, ScInterpreterContext* 
context);
 
     /// Remove from document structure and delete (!) cache on modify hint.
     virtual void Notify(const SfxHint& rHint) override;
@@ -52,16 +55,24 @@ public:
 
     ScSortedRangeCacheMap& getCacheMap() const { return mCacheMap; }
 
+    enum class ValueType
+    {
+        Values,
+        StringsCaseSensitive,
+        StringsCaseInsensitive
+    };
     struct HashKey
     {
         ScRange range;
         bool descending;
+        ValueType type;
         bool operator==(const HashKey& other) const
         {
-            return range == other.range && descending == other.descending;
+            return range == other.range && descending == other.descending && 
type == other.type;
         }
     };
-    HashKey getHashKey() const { return { maRange, mDescending }; }
+    HashKey getHashKey() const { return { maRange, mDescending, mValues }; }
+    static HashKey makeHashKey(const ScRange& range, const ScQueryParam& 
param);
 
     struct Hash
     {
@@ -69,8 +80,8 @@ public:
         {
             // Range should be just one column.
             size_t hash = key.range.hashStartColumn();
-            if (key.descending)
-                hash = ~hash;
+            o3tl::hash_combine(hash, key.descending);
+            o3tl::hash_combine(hash, key.type);
             return hash;
         }
     };
@@ -93,6 +104,7 @@ private:
     ScDocument* mpDoc;
     ScSortedRangeCacheMap& mCacheMap;
     bool mDescending;
+    ValueType mValues;
 
     ScSortedRangeCache(const ScSortedRangeCache&) = delete;
     ScSortedRangeCache& operator=(const ScSortedRangeCache&) = delete;
diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx
index a352c3c34704..83137e93ac37 100644
--- a/sc/source/core/data/documen2.cxx
+++ b/sc/source/core/data/documen2.cxx
@@ -1194,17 +1194,18 @@ ScLookupCache & ScDocument::GetLookupCache( const 
ScRange & rRange, ScInterprete
     return *pCache;
 }
 
-ScSortedRangeCache& ScDocument::GetSortedRangeCache( const ScRange & rRange, 
bool bDescending, ScInterpreterContext* pContext )
+ScSortedRangeCache& ScDocument::GetSortedRangeCache( const ScRange & rRange, 
const ScQueryParam& param,
+                                                     ScInterpreterContext* 
pContext )
 {
     ScSortedRangeCache* pCache = nullptr;
     if (!pContext->mxScSortedRangeCache)
         pContext->mxScSortedRangeCache.reset(new ScSortedRangeCacheMap);
     ScSortedRangeCacheMap* pCacheMap = pContext->mxScSortedRangeCache.get();
     // insert with temporary value to avoid doing two lookups
-    auto [findIt, bInserted] = 
pCacheMap->aCacheMap.emplace(ScSortedRangeCache::HashKey{rRange, bDescending}, 
nullptr);
+    auto [findIt, bInserted] = 
pCacheMap->aCacheMap.emplace(ScSortedRangeCache::makeHashKey(rRange, param), 
nullptr);
     if (bInserted)
     {
-        findIt->second = std::make_unique<ScSortedRangeCache>(this, rRange, 
bDescending, *pCacheMap);
+        findIt->second = std::make_unique<ScSortedRangeCache>(this, rRange, 
param, *pCacheMap, pContext);
         pCache = findIt->second.get();
         // The StartListeningArea() call is not thread-safe, as all threads
         // would access the same SvtBroadcaster.
diff --git a/sc/source/core/data/queryevaluator.cxx 
b/sc/source/core/data/queryevaluator.cxx
index dd5d73897e0c..0d55c226bc62 100644
--- a/sc/source/core/data/queryevaluator.cxx
+++ b/sc/source/core/data/queryevaluator.cxx
@@ -68,6 +68,25 @@ bool ScQueryEvaluator::isTextMatchOp(const ScQueryEntry& 
rEntry)
     return false;
 }
 
+bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, const 
ScQueryEntry& rEntry)
+{
+    bool bMatchWholeCell = docMatchWholeCell;
+    if (isPartialTextMatchOp(rEntry))
+        // may have to do partial textural comparison.
+        bMatchWholeCell = false;
+    return bMatchWholeCell;
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(const ScQueryEntry& rEntry) const
+{
+    return isMatchWholeCellHelper(mbMatchWholeCell, rEntry);
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(const ScDocument& rDoc, const 
ScQueryEntry& rEntry)
+{
+    return isMatchWholeCellHelper(rDoc.GetDocOptions().IsMatchWholeCell(), 
rEntry);
+}
+
 void ScQueryEvaluator::setupTransliteratorIfNeeded()
 {
     if (!mpTransliteration)
@@ -322,16 +341,11 @@ bool ScQueryEvaluator::isFastCompareByString(const 
ScQueryEntry& rEntry) const
     // 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;
+    return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
+           && isMatchWholeCell(rEntry);
 }
 
 // The value is placed inside one parameter: [pValueSource1] or 
[pValueSource2] but never in both.
@@ -348,13 +362,7 @@ std::pair<bool, bool> 
ScQueryEvaluator::compareByString(const ScQueryEntry& rEnt
     if (bFast)
         bMatchWholeCell = true;
     else
-    {
-        bMatchWholeCell = mbMatchWholeCell;
-        if (isPartialTextMatchOp(rEntry))
-            // may have to do partial textural comparison.
-            bMatchWholeCell = false;
-    }
-
+        bMatchWholeCell = isMatchWholeCell(rEntry);
     const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
     const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
 
diff --git a/sc/source/core/data/queryiter.cxx 
b/sc/source/core/data/queryiter.cxx
index 280926bf6eb0..75a2fe28b7a6 100644
--- a/sc/source/core/data/queryiter.cxx
+++ b/sc/source/core/data/queryiter.cxx
@@ -257,7 +257,7 @@ void ScQueryCellIteratorBase< accessType, queryType 
>::InitPos()
                 lastRow = nRow;
                 ScQueryOp saveOp = op;
                 op = SC_LESS;
-                if( BinarySearch( nCol ))
+                if( BinarySearch( nCol, true ))
                     beforeRow = nRow;
                 // If BinarySearch() returns false, there was no match, which 
means
                 // there's no value smaller. In that case BinarySearch() has 
set
@@ -316,7 +316,7 @@ void decBlock(std::pair<Iter, size_t>& rPos)
 }
 
 template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType 
queryType >
-bool ScQueryCellIteratorBase< accessType, queryType >::BinarySearch( SCCOL col 
)
+bool ScQueryCellIteratorBase< accessType, queryType >::BinarySearch( SCCOL 
col, bool forEqual )
 {
     assert(maParam.GetEntry(0).bDoQuery && !maParam.GetEntry(1).bDoQuery
         && maParam.GetEntry(0).GetQueryItems().size() == 1 );
@@ -347,7 +347,7 @@ bool ScQueryCellIteratorBase< accessType, queryType 
>::BinarySearch( SCCOL col )
     const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
     bool bAscending = rEntry.eOp == SC_LESS || rEntry.eOp == SC_LESS_EQUAL || 
rEntry.eOp == SC_EQUAL;
     bool bByString = rItem.meType == ScQueryEntry::ByString;
-    bool bForceStr = bByString && rEntry.eOp == SC_EQUAL;
+    bool bForceStr = bByString && ( rEntry.eOp == SC_EQUAL || forEqual );
     bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString;
     bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
         !maParam.bHasHeader && bByString;
@@ -1072,10 +1072,8 @@ void ScQueryCellIteratorAccessSpecific< 
ScQueryCellIteratorAccess::SortedCache >
 void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache 
>::InitPosStart()
 {
     ScRange aSortedRangeRange( nCol, maParam.nRow1, nTab, nCol, maParam.nRow2, 
nTab );
-    ScQueryOp& op = maParam.GetEntry(0).eOp;
     // We want all matching values first in the sort order,
-    bool descending = op == SC_GREATER || op == SC_GREATER_EQUAL;
-    SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, 
descending, &mrContext ));
+    SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, 
&mrContext ));
     // InitPosFinish() needs to be called after this, 
ScQueryCellIteratorBase::InitPos()
     // will handle that
 }
@@ -1193,19 +1191,25 @@ ScQueryCellIteratorAccessSpecific< 
ScQueryCellIteratorAccess::SortedCache >::Mak
     return SortedCacheIndexer(rCells, nStartRow, nEndRow, sortedCache);
 }
 
-static bool CanBeUsedForSorterCache(const ScQueryParam& rParam)
+static bool CanBeUsedForSorterCache(const ScDocument& rDoc, const 
ScQueryParam& rParam)
 {
     if(!rParam.GetEntry(0).bDoQuery || rParam.GetEntry(1).bDoQuery
         || rParam.GetEntry(0).GetQueryItems().size() != 1 )
         return false;
     if(rParam.eSearchType != utl::SearchParam::SearchType::Normal)
         return false;
-    if(rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByValue)
+    if(rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByValue
+        && rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByString)
         return false;
     if(!rParam.bByRow)
         return false;
     if(rParam.bHasHeader)
         return false;
+    if(rParam.GetEntry(0).GetQueryItem().mbMatchEmpty)
+        return false;
+    if(rParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+        && !ScQueryEvaluator::isMatchWholeCell(rDoc, rParam.GetEntry(0)))
+        return false; // substring matching cannot be sorted
     if(rParam.GetEntry(0).eOp != SC_LESS && rParam.GetEntry(0).eOp != 
SC_LESS_EQUAL
         && rParam.GetEntry(0).eOp != SC_GREATER && rParam.GetEntry(0).eOp != 
SC_GREATER_EQUAL
         && rParam.GetEntry(0).eOp != SC_EQUAL)
@@ -1249,9 +1253,9 @@ bool ScQueryCellIterator< accessType >::GetNext()
     return GetThis();
 }
 
-bool ScQueryCellIteratorSortedCache::CanBeUsed(const ScQueryParam& rParam)
+bool ScQueryCellIteratorSortedCache::CanBeUsed(const ScDocument& rDoc, const 
ScQueryParam& rParam)
 {
-    return CanBeUsedForSorterCache(rParam);
+    return CanBeUsedForSorterCache(rDoc, rParam);
 }
 
 // Countifs implementation.
@@ -1278,9 +1282,9 @@ sal_uInt64 ScCountIfCellIterator< accessType >::GetCount()
 }
 
 
-bool ScCountIfCellIteratorSortedCache::CanBeUsed(const ScQueryParam& rParam)
+bool ScCountIfCellIteratorSortedCache::CanBeUsed(const ScDocument& rDoc, const 
ScQueryParam& rParam)
 {
-    return CanBeUsedForSorterCache(rParam);
+    return CanBeUsedForSorterCache(rDoc, rParam);
 }
 
 template<>
@@ -1297,9 +1301,7 @@ sal_uInt64 ScCountIfCellIterator< 
ScQueryCellIteratorAccess::SortedCache >::GetC
         nRow = maParam.nRow1;
         ScRange aSortedRangeRange( col, maParam.nRow1, nTab, col, 
maParam.nRow2, nTab);
         ScQueryOp& op = maParam.GetEntry(0).eOp;
-        // We want all matching values to start in the sort order.
-        bool descending = op == SC_GREATER || op == SC_GREATER_EQUAL;
-        SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, 
descending, &mrContext ));
+        SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, 
maParam, &mrContext ));
         if( op == SC_EQUAL )
         {
             // BinarySearch() searches for the last item that matches. 
Therefore first
@@ -1307,7 +1309,7 @@ sal_uInt64 ScCountIfCellIterator< 
ScQueryCellIteratorAccess::SortedCache >::GetC
             // matching position using SC_EQUAL.
             ScQueryOp saveOp = op;
             op = SC_LESS;
-            if( BinarySearch( nCol ))
+            if( BinarySearch( nCol, true ))
             {
                 op = saveOp; // back to SC_EQUAL
                 size_t lastNonMatching = sortedCache->indexForRow(nRow);
diff --git a/sc/source/core/tool/interpr1.cxx b/sc/source/core/tool/interpr1.cxx
index 92b4de1b3443..e80b299f4771 100644
--- a/sc/source/core/tool/interpr1.cxx
+++ b/sc/source/core/tool/interpr1.cxx
@@ -5788,7 +5788,7 @@ void ScInterpreter::ScCountIf()
             }
             else
             {
-                if(ScCountIfCellIteratorSortedCache::CanBeUsed(rParam))
+                if(ScCountIfCellIteratorSortedCache::CanBeUsed(mrDoc, rParam))
                 {
                     ScCountIfCellIteratorSortedCache aCellIter(mrDoc, 
mrContext, nTab1, rParam, false);
                     fCount += aCellIter.GetCount();
@@ -6150,7 +6150,7 @@ void ScInterpreter::IterateParametersIfs( 
double(*ResultFunc)( const sc::ParamIf
             }
             else
             {
-                if( ScQueryCellIteratorSortedCache::CanBeUsed( rParam ))
+                if( ScQueryCellIteratorSortedCache::CanBeUsed( mrDoc, rParam ))
                 {
                     ScQueryCellIteratorSortedCache aCellIter(mrDoc, mrContext, 
nTab1, rParam, false);
                     // Increment Entry.nField in iterator when switching to 
next column.
@@ -9984,7 +9984,7 @@ static bool lcl_LookupQuery( ScAddress & o_rResultPos, 
ScDocument& rDoc, ScInter
     }
     else // EQUAL
     {
-        if( ScQueryCellIteratorSortedCache::CanBeUsed( rParam ))
+        if( ScQueryCellIteratorSortedCache::CanBeUsed( rDoc, rParam ))
         {
             ScQueryCellIteratorSortedCache aCellIter( rDoc, rContext, 
rParam.nTab, rParam, false);
             if (aCellIter.GetFirst())
diff --git a/sc/source/core/tool/rangecache.cxx 
b/sc/source/core/tool/rangecache.cxx
index 0bfeb971969c..93c831e2a914 100644
--- a/sc/source/core/tool/rangecache.cxx
+++ b/sc/source/core/tool/rangecache.cxx
@@ -21,32 +21,112 @@
 #include <cellvalue.hxx>
 #include <document.hxx>
 #include <brdcst.hxx>
+#include <queryevaluator.hxx>
+#include <queryparam.hxx>
 
 #include <sal/log.hxx>
+#include <unotools/collatorwrapper.hxx>
 
-ScSortedRangeCache::ScSortedRangeCache(ScDocument* pDoc, const ScRange& 
rRange, bool bDescending,
-                                       ScSortedRangeCacheMap& cacheMap)
+static bool needsDescending(const ScQueryParam& param)
+{
+    assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
+           && param.GetEntry(0).GetQueryItems().size() == 1);
+    assert(param.GetEntry(0).eOp == SC_GREATER || param.GetEntry(0).eOp == 
SC_GREATER_EQUAL
+           || param.GetEntry(0).eOp == SC_LESS || param.GetEntry(0).eOp == 
SC_LESS_EQUAL
+           || param.GetEntry(0).eOp == SC_EQUAL);
+    // We want all matching values to start in the sort order,
+    // since the data is searched from start until the last matching one.
+    return param.GetEntry(0).eOp == SC_GREATER || param.GetEntry(0).eOp == 
SC_GREATER_EQUAL;
+}
+
+static ScSortedRangeCache::ValueType toValueType(const ScQueryParam& param)
+{
+    assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
+           && param.GetEntry(0).GetQueryItems().size() == 1);
+    assert(param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+           || param.GetEntry(0).GetQueryItem().meType == 
ScQueryEntry::ByValue);
+    if (param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue)
+        return ScSortedRangeCache::ValueType::Values;
+    return param.bCaseSens ? 
ScSortedRangeCache::ValueType::StringsCaseSensitive
+                           : 
ScSortedRangeCache::ValueType::StringsCaseInsensitive;
+}
+
+ScSortedRangeCache::ScSortedRangeCache(ScDocument* pDoc, const ScRange& rRange,
+                                       const ScQueryParam& param, 
ScSortedRangeCacheMap& cacheMap,
+                                       ScInterpreterContext* context)
     : maRange(rRange)
     , mpDoc(pDoc)
     , mCacheMap(cacheMap)
-    , mDescending(bDescending)
+    , mDescending(needsDescending(param))
+    , mValues(toValueType(param))
 {
     assert(maRange.aStart.Col() == maRange.aEnd.Col());
     assert(maRange.aStart.Tab() == maRange.aEnd.Tab());
     SCTAB nTab = maRange.aStart.Tab();
     SCTAB nCol = maRange.aStart.Col();
-    for (SCROW nRow = maRange.aStart.Row(); nRow <= maRange.aEnd.Row(); ++nRow)
+    assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery
+           && param.GetEntry(0).GetQueryItems().size() == 1);
+    const ScQueryEntry& entry = param.GetEntry(0);
+    const ScQueryEntry::Item& item = entry.GetQueryItem();
+    if (mValues == ValueType::Values)
     {
-        ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, 
nTab)));
-        if (!cell.hasError() && cell.hasNumeric())
-            mSortedRows.push_back(nRow);
+        struct RowData
+        {
+            SCROW row;
+            double value;
+        };
+        std::vector<RowData> rowData;
+        for (SCROW nRow = maRange.aStart.Row(); nRow <= maRange.aEnd.Row(); 
++nRow)
+        {
+            ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, 
nTab)));
+            if (ScQueryEvaluator::isQueryByValue(entry, item, cell))
+                rowData.push_back(RowData{ nRow, cell.getValue() });
+        }
+        std::stable_sort(rowData.begin(), rowData.end(),
+                         [](const RowData& d1, const RowData& d2) { return 
d1.value < d2.value; });
+        if (mDescending)
+            for (auto it = rowData.rbegin(); it != rowData.rend(); ++it)
+                mSortedRows.emplace_back(it->row);
+        else
+            for (const RowData& d : rowData)
+                mSortedRows.emplace_back(d.row);
+    }
+    else
+    {
+        struct RowData
+        {
+            SCROW row;
+            OUString string;
+        };
+        std::vector<RowData> rowData;
+        // Try to reuse as much ScQueryEvaluator code as possible, this should
+        // basically do the same comparisons.
+        ScQueryEvaluator evaluator(*pDoc, *pDoc->FetchTable(nTab), param, 
context);
+        for (SCROW nRow = maRange.aStart.Row(); nRow <= maRange.aEnd.Row(); 
++nRow)
+        {
+            ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, 
nTab)));
+            if (ScQueryEvaluator::isQueryByString(entry, item, cell))
+            {
+                const svl::SharedString* sharedString = nullptr;
+                OUString string = evaluator.getCellString(cell, nRow, entry, 
&sharedString);
+                if (sharedString)
+                    string = sharedString->getString();
+                rowData.push_back(RowData{ nRow, string });
+            }
+        }
+        CollatorWrapper& collator
+            = ScGlobal::GetCollator(mValues == 
ValueType::StringsCaseSensitive);
+        std::stable_sort(rowData.begin(), rowData.end(),
+                         [&collator](const RowData& d1, const RowData& d2) {
+                             return collator.compareString(d1.string, 
d2.string) < 0;
+                         });
+        if (mDescending)
+            for (auto it = rowData.rbegin(); it != rowData.rend(); ++it)
+                mSortedRows.emplace_back(it->row);
+        else
+            for (const RowData& d : rowData)
+                mSortedRows.emplace_back(d.row);
     }
-    std::stable_sort(
-        mSortedRows.begin(), mSortedRows.end(), [pDoc, nCol, nTab](SCROW 
nRow1, SCROW nRow2) {
-            return pDoc->GetValue(nCol, nRow1, nTab) < pDoc->GetValue(nCol, 
nRow2, nTab);
-        });
-    if (mDescending)
-        std::reverse(mSortedRows.begin(), mSortedRows.end());
 }
 
 void ScSortedRangeCache::Notify(const SfxHint& rHint)
@@ -63,4 +143,10 @@ void ScSortedRangeCache::Notify(const SfxHint& rHint)
     }
 }
 
+ScSortedRangeCache::HashKey ScSortedRangeCache::makeHashKey(const ScRange& 
range,
+                                                            const 
ScQueryParam& param)
+{
+    return { range, needsDescending(param), toValueType(param) };
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to