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: */