1851 lines
66 KiB
C++
1851 lines
66 KiB
C++
/* -*- 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 <queryiter.hxx>
|
|
#include <rtl/math.hxx>
|
|
|
|
#include <comphelper/flagguard.hxx>
|
|
#include <o3tl/safeint.hxx>
|
|
#include <svl/numformat.hxx>
|
|
|
|
#include <global.hxx>
|
|
#include <document.hxx>
|
|
#include <table.hxx>
|
|
#include <column.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <cellform.hxx>
|
|
#include <queryparam.hxx>
|
|
#include <queryentry.hxx>
|
|
#include <cellvalue.hxx>
|
|
#include <queryevaluator.hxx>
|
|
#include <rangecache.hxx>
|
|
#include <refdata.hxx>
|
|
|
|
#include <svl/sharedstring.hxx>
|
|
#include <unotools/collatorwrapper.hxx>
|
|
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
ScQueryCellIteratorBase< accessType, queryType >::ScQueryCellIteratorBase(ScDocument& rDocument,
|
|
ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod, bool bReverse )
|
|
: AccessBase( rDocument, rContext, rParam, bReverse )
|
|
, nStopOnMismatch( nStopOnMismatchDisabled )
|
|
, nTestEqualCondition( nTestEqualConditionDisabled )
|
|
, nSortedBinarySearch( nBinarySearchDisabled )
|
|
, bAdvanceQuery( false )
|
|
, bIgnoreMismatchOnLeadingStrings( false )
|
|
, nSearchOpCode( SC_OPCODE_NONE )
|
|
, nBestFitCol(SCCOL_MAX)
|
|
, nBestFitRow(SCROW_MAX)
|
|
{
|
|
nTab = nTable;
|
|
nCol = !bReverse ? maParam.nCol1 : maParam.nCol2;
|
|
nRow = !bReverse ? maParam.nRow1 : maParam.nRow2;
|
|
SCSIZE i;
|
|
if (!bMod) // Or else it's already inserted
|
|
return;
|
|
|
|
SCSIZE nCount = maParam.GetEntryCount();
|
|
for (i = 0; (i < nCount) && (maParam.GetEntry(i).bDoQuery); ++i)
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry(i);
|
|
ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
|
|
sal_uInt32 nIndex = 0;
|
|
bool bNumber = mrContext.NFIsNumberFormat(
|
|
rItem.maString.getString(), nIndex, rItem.mfVal);
|
|
rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
|
|
}
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
|
|
{
|
|
assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
|
|
const ScQueryEntry& rEntry = maParam.GetEntry(0);
|
|
const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
|
|
|
|
const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1;
|
|
SCCOLROW nFirstQueryField = rEntry.nField;
|
|
bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings &&
|
|
rItem.meType != ScQueryEntry::ByString;
|
|
bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
|
|
!maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
|
|
((maParam.bByRow && nRow == maParam.nRow1) ||
|
|
(!maParam.bByRow && nCol == maParam.nCol1));
|
|
bool bTestEqualCondition = false;
|
|
const bool bNewSearchFunction = nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH;
|
|
ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], maParam, &mrContext,
|
|
(nTestEqualCondition ? &bTestEqualCondition : nullptr), bNewSearchFunction);
|
|
if( queryType == ScQueryCellIteratorType::CountIf )
|
|
{
|
|
// These are not used for COUNTIF, so should not be set, make the compiler
|
|
// explicitly aware of it so that the relevant parts are optimized away.
|
|
assert( !bAllStringIgnore );
|
|
assert( !bIgnoreMismatchOnLeadingStrings );
|
|
assert( nStopOnMismatch == nStopOnMismatchDisabled );
|
|
assert( nTestEqualCondition == nTestEqualConditionDisabled );
|
|
bAllStringIgnore = false;
|
|
bIgnoreMismatchOnLeadingStrings = false;
|
|
nStopOnMismatch = nStopOnMismatchDisabled;
|
|
nTestEqualCondition = nTestEqualConditionDisabled;
|
|
// This one is always set.
|
|
assert( bAdvanceQuery );
|
|
bAdvanceQuery = true;
|
|
}
|
|
|
|
const ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
while (true)
|
|
{
|
|
bool bNextColumn = maCurPos.first == pCol->maCells.end();
|
|
if (!bNextColumn)
|
|
{
|
|
if ((!mbReverseSearch && nRow > maParam.nRow2) || (mbReverseSearch && nRow < maParam.nRow1))
|
|
bNextColumn = true;
|
|
}
|
|
|
|
if (bNextColumn)
|
|
{
|
|
do
|
|
{
|
|
if (!mbReverseSearch)
|
|
{
|
|
++nCol;
|
|
if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
--nCol;
|
|
if (nCol < maParam.nCol1 || nCol < static_cast<SCCOL>(0))
|
|
return;
|
|
}
|
|
if ( bAdvanceQuery )
|
|
{
|
|
AdvanceQueryParamEntryField();
|
|
nFirstQueryField = rEntry.nField;
|
|
}
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
}
|
|
while (!rItem.mbMatchEmpty && pCol->IsEmptyData());
|
|
|
|
InitPos();
|
|
|
|
bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
|
|
!maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
|
|
maParam.bByRow;
|
|
}
|
|
|
|
if (maCurPos.first->type == sc::element_type_empty)
|
|
{
|
|
if (rItem.mbMatchEmpty && bSingleQueryItem)
|
|
{
|
|
// This shortcut, instead of determining if any SC_OR query
|
|
// exists or this query is SC_AND'ed (which wouldn't make
|
|
// sense, but..) and evaluating them in ValidQuery(), is
|
|
// possible only because the interpreter is the only caller
|
|
// that sets mbMatchEmpty and there is only one item in those
|
|
// cases.
|
|
// XXX this would have to be reworked if other filters used it
|
|
// in different manners and evaluation would have to be done in
|
|
// ValidQuery().
|
|
if(HandleItemFound())
|
|
return;
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
!mbReverseSearch ? IncBlock() : DecBlock();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
|
|
|
|
if (bAllStringIgnore && aCell.hasString())
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
else
|
|
{
|
|
if ( queryEvaluator.ValidQuery( nRow,
|
|
(nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : nullptr)))
|
|
{
|
|
if ( nTestEqualCondition && bTestEqualCondition )
|
|
nTestEqualCondition |= nTestEqualConditionMatched;
|
|
if ( aCell.isEmpty())
|
|
return;
|
|
|
|
// XLookUp/XMatch: Forward/asc/backward/desc search for best fit value, except if we have an exact match
|
|
if (bNewSearchFunction &&
|
|
(rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL) &&
|
|
(nBestFitCol != nCol || nBestFitRow != nRow))
|
|
{
|
|
bool bNumSearch = rItem.meType == ScQueryEntry::ByValue && aCell.hasNumeric();
|
|
bool bStringSearch = rItem.meType == ScQueryEntry::ByString && aCell.hasString();
|
|
if (bNumSearch || bStringSearch)
|
|
{
|
|
if (nTestEqualCondition == nTestEqualConditionFulfilled || (nBestFitCol == SCCOL_MAX && nBestFitRow == SCROW_MAX))
|
|
HandleBestFitItemFound(nCol, nRow);
|
|
else
|
|
{
|
|
ScAddress aBFAddr(nBestFitCol, nBestFitRow, nTab);
|
|
ScRefCellValue aBFCell(rDoc, aBFAddr);
|
|
ScQueryParam aParamTmp(maParam);
|
|
ScQueryEntry& rEntryTmp = aParamTmp.GetEntry(0);
|
|
|
|
if (rEntry.eOp == SC_LESS_EQUAL)
|
|
rEntryTmp.eOp = SC_GREATER;
|
|
else if (rEntry.eOp == SC_GREATER_EQUAL)
|
|
rEntryTmp.eOp = SC_LESS;
|
|
|
|
ScQueryEntry::Item& rItemTmp = rEntryTmp.GetQueryItem();
|
|
if (bNumSearch)
|
|
rItemTmp.mfVal = aBFCell.getValue();
|
|
else if (bStringSearch)
|
|
rItemTmp.maString = svl::SharedString(aBFCell.getString(&rDoc));
|
|
|
|
ScQueryEvaluator queryEvaluatorTmp(rDoc, *rDoc.maTabs[nTab], aParamTmp, &mrContext, nullptr, bNewSearchFunction);
|
|
if (queryEvaluatorTmp.ValidQuery(nRow, (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : nullptr)))
|
|
HandleBestFitItemFound(nCol, nRow);
|
|
else
|
|
{
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
continue;
|
|
}
|
|
}
|
|
if (HandleItemFound())
|
|
return;
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
continue;
|
|
}
|
|
else if ( nStopOnMismatch )
|
|
{
|
|
// Yes, even a mismatch may have a fulfilled equal
|
|
// condition if regular expressions were involved and
|
|
// SC_LESS_EQUAL or SC_GREATER_EQUAL were queried.
|
|
if ( nTestEqualCondition && bTestEqualCondition )
|
|
{
|
|
nTestEqualCondition |= nTestEqualConditionMatched;
|
|
nStopOnMismatch |= nStopOnMismatchOccurred;
|
|
return;
|
|
}
|
|
bool bStop;
|
|
if (bFirstStringIgnore)
|
|
{
|
|
if (aCell.hasString())
|
|
{
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
bStop = false;
|
|
}
|
|
else
|
|
bStop = true;
|
|
}
|
|
else
|
|
bStop = true;
|
|
if (bStop)
|
|
{
|
|
nStopOnMismatch |= nStopOnMismatchOccurred;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
!mbReverseSearch ? IncPos() : DecPos();
|
|
}
|
|
bFirstStringIgnore = false;
|
|
}
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
void ScQueryCellIteratorBase< accessType, queryType >::InitPos()
|
|
{
|
|
if constexpr( accessType != ScQueryCellIteratorAccess::SortedCache )
|
|
AccessBase::InitPos();
|
|
else
|
|
{
|
|
// This should be all in AccessBase::InitPos(), but that one can't call
|
|
// BinarySearch(), so do it this way instead.
|
|
bool bNewSearchFunction = nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH;
|
|
AccessBase::InitPosStart(bNewSearchFunction, nSortedBinarySearch);
|
|
ScQueryOp& op = maParam.GetEntry(0).eOp;
|
|
SCCOLROW beforeColRow = -1;
|
|
SCCOLROW lastColRow = -1;
|
|
if( op == SC_EQUAL )
|
|
{
|
|
if( BinarySearch( maParam.bByRow ? nCol : nRow) )
|
|
{
|
|
// BinarySearch() searches for the last item that matches. Now we
|
|
// also need to find the first item where to start. Find the last
|
|
// non-matching position using SC_LESS and the start position
|
|
// is the one after it.
|
|
lastColRow = maParam.bByRow ? nRow : nCol;
|
|
ScQueryOp saveOp = op;
|
|
op = SC_LESS;
|
|
if( BinarySearch(maParam.bByRow ? nCol : nRow, true) )
|
|
beforeColRow = maParam.bByRow ? nRow : nCol;
|
|
// If BinarySearch() returns false, there was no match, which means
|
|
// there's no value smaller. In that case BinarySearch() has set
|
|
// the position to the first row in the range.
|
|
op = saveOp; // back to SC_EQUAL
|
|
}
|
|
else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
|
|
&& rDoc.IsEmptyData(maParam.nCol1, maParam.nRow1, maParam.nCol2, maParam.nRow2, nTab))
|
|
{
|
|
// BinarySearch() returns false in case it's all empty data,
|
|
// handle that specially.
|
|
lastColRow = maParam.nRow2;
|
|
}
|
|
if (maParam.bByRow)
|
|
AccessBase::InitPosFinish(beforeColRow, lastColRow, false/*bFirstMatch*/);
|
|
else
|
|
AccessBase::InitPosColFinish(beforeColRow, lastColRow, false/*bFirstMatch*/);
|
|
}
|
|
else
|
|
{ // The range is from the start up to and including the last matching.
|
|
if( BinarySearch( maParam.bByRow ? nCol : nRow) )
|
|
{
|
|
lastColRow = maParam.bByRow ? nRow : nCol;
|
|
if (nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH)
|
|
{
|
|
ScQueryOp saveOp = op;
|
|
op = SC_LESS;
|
|
if( BinarySearch(maParam.bByRow ? nCol : nRow, true) )
|
|
beforeColRow = maParam.bByRow ? nRow : nCol;
|
|
op = saveOp;
|
|
}
|
|
}
|
|
if ((nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH) &&
|
|
(lastColRow == beforeColRow || beforeColRow == -1))
|
|
{
|
|
beforeColRow = -1;
|
|
if (maParam.bByRow)
|
|
AccessBase::InitPosFinish(beforeColRow, lastColRow, true/*bFirstMatch*/);
|
|
else
|
|
{
|
|
AccessBase::InitPosColFinish(beforeColRow, lastColRow, true/*bFirstMatch*/);
|
|
AdvanceQueryParamEntryFieldForBinarySearch();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (maParam.bByRow)
|
|
AccessBase::InitPosFinish(beforeColRow, lastColRow, false/*bFirstMatch*/);
|
|
else
|
|
{
|
|
AccessBase::InitPosColFinish(beforeColRow, lastColRow, false/*bFirstMatch*/);
|
|
AdvanceQueryParamEntryFieldForBinarySearch();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
void ScQueryCellIteratorBase< accessType, queryType >::AdvanceQueryParamEntryField()
|
|
{
|
|
SCSIZE nEntries = maParam.GetEntryCount();
|
|
for ( SCSIZE j = 0; j < nEntries; j++ )
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if ( rEntry.bDoQuery )
|
|
{
|
|
if (!mbReverseSearch && rEntry.nField < rDoc.MaxCol())
|
|
rEntry.nField++;
|
|
else if (mbReverseSearch && rEntry.nField > static_cast<SCCOLROW>(0))
|
|
rEntry.nField--;
|
|
else
|
|
{
|
|
assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL || --rEntry.nField < 0");
|
|
}
|
|
}
|
|
else
|
|
break; // for
|
|
}
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
void ScQueryCellIteratorBase< accessType, queryType >::AdvanceQueryParamEntryFieldForBinarySearch()
|
|
{
|
|
SCSIZE nEntries = maParam.GetEntryCount();
|
|
for ( SCSIZE j = 0; j < nEntries; j++ )
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if ( rEntry.bDoQuery )
|
|
{
|
|
if (rEntry.nField < rDoc.MaxCol())
|
|
rEntry.nField = nCol;
|
|
else
|
|
{
|
|
assert(!"AdvanceQueryParamEntryFieldForBinarySearch: rEntry.nField >= MAXCOL");
|
|
}
|
|
}
|
|
else
|
|
break; // for
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
template<typename Iter>
|
|
void incBlock(std::pair<Iter, size_t>& rPos)
|
|
{
|
|
// Move to the next block.
|
|
++rPos.first;
|
|
rPos.second = 0;
|
|
}
|
|
|
|
template<typename Iter>
|
|
void decBlock(std::pair<Iter, size_t>& rPos)
|
|
{
|
|
// Move to the last element of the previous block.
|
|
--rPos.first;
|
|
rPos.second = rPos.first->size - 1;
|
|
}
|
|
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
|
|
bool ScQueryCellIteratorBase< accessType, queryType >::BinarySearch( SCCOLROW col_row, bool forEqual )
|
|
{
|
|
assert(maParam.GetEntry(0).bDoQuery && !maParam.GetEntry(1).bDoQuery
|
|
&& maParam.GetEntry(0).GetQueryItems().size() == 1 );
|
|
assert(maParam.eSearchType == utl::SearchParam::SearchType::Normal);
|
|
assert(maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
|
|
|| maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue);
|
|
assert(maParam.GetEntry(0).eOp == SC_LESS || maParam.GetEntry(0).eOp == SC_LESS_EQUAL
|
|
|| maParam.GetEntry(0).eOp == SC_GREATER || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL
|
|
|| maParam.GetEntry(0).eOp == SC_EQUAL);
|
|
|
|
// TODO: This will be extremely slow with mdds::multi_type_vector.
|
|
|
|
assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
|
|
nCol = maParam.bByRow ? col_row : maParam.nCol1;
|
|
nRow = maParam.bByRow ? maParam.nRow1 : col_row;
|
|
|
|
if (maParam.bByRow)
|
|
{
|
|
if (nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (maParam.nCol2 >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
|
|
return false;
|
|
}
|
|
|
|
const ScColumn* pCol = nullptr;
|
|
if (maParam.bByRow)
|
|
{
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
if (pCol->IsEmptyData())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
}
|
|
|
|
CollatorWrapper& rCollator = ScGlobal::GetCollator(maParam.bCaseSens);
|
|
const ScQueryEntry& rEntry = maParam.GetEntry(0);
|
|
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 || forEqual );
|
|
bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString;
|
|
bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
|
|
!maParam.bHasHeader && bByString;
|
|
|
|
if (maParam.bHasHeader)
|
|
maParam.bByRow ? ++nRow : ++nCol;
|
|
|
|
if (bFirstStringIgnore)
|
|
{
|
|
// move to next col if necessary
|
|
if (!maParam.bByRow && maParam.nCol1 != nCol)
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
|
|
sc::CellStoreType::const_position_type aPos = pCol->maCells.position(nRow);
|
|
if (aPos.first->type == sc::element_type_string || aPos.first->type == sc::element_type_edittext)
|
|
{
|
|
ScRefCellValue aCell = sc::toRefCell(aPos.first, aPos.second);
|
|
sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, nRow);
|
|
OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, &mrContext, rDoc);
|
|
sal_Int32 nTmp = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
|
|
if ((rEntry.eOp == SC_LESS_EQUAL && nTmp > 0) ||
|
|
(rEntry.eOp == SC_GREATER_EQUAL && nTmp < 0) ||
|
|
(rEntry.eOp == SC_EQUAL && nTmp != 0) ||
|
|
(rEntry.eOp == SC_LESS && nTmp >= 0) ||
|
|
(rEntry.eOp == SC_GREATER && nTmp <= 0))
|
|
maParam.bByRow ? ++nRow : ++nCol;
|
|
}
|
|
}
|
|
|
|
sc::CellStoreType::const_position_type startPos;
|
|
// Skip leading empty block, if any.
|
|
if (maParam.bByRow)
|
|
{
|
|
startPos = pCol->maCells.position(nRow);
|
|
if (startPos.first->type == sc::element_type_empty)
|
|
incBlock(startPos);
|
|
}
|
|
else
|
|
{
|
|
bool bNonEmpty = false;
|
|
while (nCol != maParam.nCol2 && !bNonEmpty)
|
|
{
|
|
if (maParam.nCol1 != nCol)
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
startPos = pCol->maCells.position(nRow);
|
|
if (startPos.first->type == sc::element_type_empty)
|
|
++nCol;
|
|
else
|
|
bNonEmpty = true;
|
|
}
|
|
}
|
|
|
|
if(bAllStringIgnore)
|
|
{
|
|
// Skip all leading string or empty blocks.
|
|
if (maParam.bByRow)
|
|
{
|
|
while (startPos.first != pCol->maCells.end()
|
|
&& (startPos.first->type == sc::element_type_string ||
|
|
startPos.first->type == sc::element_type_edittext ||
|
|
startPos.first->type == sc::element_type_empty))
|
|
{
|
|
incBlock(startPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bSkipped = false;
|
|
while (nCol != maParam.nCol2 && !bSkipped)
|
|
{
|
|
if (maParam.nCol1 != nCol)
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
|
|
startPos = pCol->maCells.position(nRow);
|
|
if (startPos.first->type == sc::element_type_string ||
|
|
startPos.first->type == sc::element_type_edittext ||
|
|
startPos.first->type == sc::element_type_empty)
|
|
++nCol;
|
|
else
|
|
bSkipped = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maParam.bByRow)
|
|
{
|
|
if (startPos.first == pCol->maCells.end())
|
|
return false;
|
|
nRow = startPos.first->position + startPos.second;
|
|
if (nRow > maParam.nRow2)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (nCol > maParam.nCol2)
|
|
return false;
|
|
}
|
|
|
|
const auto& aIndexer(maParam.bByRow ? MakeBinarySearchIndexer(&pCol->maCells, nRow, maParam.nRow2) :
|
|
MakeBinarySearchIndexer(nullptr, nCol, maParam.nCol2));
|
|
|
|
if (!aIndexer.isValid())
|
|
return false;
|
|
|
|
size_t nLo = aIndexer.getLowIndex();
|
|
size_t nHi = aIndexer.getHighIndex();
|
|
BinarySearchCellType aCellData;
|
|
|
|
// Bookkeeping values for breaking up the binary search in case the data
|
|
// range isn't strictly sorted.
|
|
size_t nLastInRange = nLo;
|
|
double fLastInRangeValue = bAscending ?
|
|
-(::std::numeric_limits<double>::max()) :
|
|
::std::numeric_limits<double>::max();
|
|
OUString aLastInRangeString;
|
|
if (!bAscending)
|
|
aLastInRangeString = OUString(u'\xFFFF');
|
|
|
|
if (maParam.bByRow)
|
|
aCellData = aIndexer.getCell(nLastInRange);
|
|
else
|
|
aCellData = aIndexer.getColCell(nLastInRange, nRow);
|
|
|
|
ScRefCellValue aCell = aCellData.first;
|
|
if (bForceStr || aCell.hasString())
|
|
{
|
|
sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, maParam.bByRow ? aCellData.second : nRow);
|
|
aLastInRangeString = ScCellFormat::GetInputString(aCell, nFormat, &mrContext, rDoc);
|
|
}
|
|
else
|
|
{
|
|
switch (aCell.getType())
|
|
{
|
|
case CELLTYPE_VALUE :
|
|
fLastInRangeValue = aCell.getDouble();
|
|
break;
|
|
case CELLTYPE_FORMULA :
|
|
fLastInRangeValue = aCell.getFormula()->GetValue();
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
}
|
|
|
|
sal_Int32 nRes = 0;
|
|
std::optional<size_t> found;
|
|
bool bDone = false;
|
|
bool orderBroken = false;
|
|
while (nLo <= nHi && !bDone)
|
|
{
|
|
size_t nMid = (nLo+nHi)/2;
|
|
size_t i = nMid;
|
|
|
|
if (maParam.bByRow)
|
|
aCellData = aIndexer.getCell(i);
|
|
else
|
|
aCellData = aIndexer.getColCell(i, nRow);
|
|
aCell = aCellData.first;
|
|
bool bStr = bForceStr || aCell.hasString();
|
|
nRes = 0;
|
|
|
|
// compares are content<query:-1, content>query:1
|
|
// Cell value comparison similar to ScTable::ValidQuery()
|
|
if (!bStr && !bByString)
|
|
{
|
|
double nCellVal;
|
|
switch (aCell.getType())
|
|
{
|
|
case CELLTYPE_VALUE :
|
|
case CELLTYPE_FORMULA :
|
|
nCellVal = aCell.getValue();
|
|
break;
|
|
default:
|
|
nCellVal = 0.0;
|
|
}
|
|
if ((nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(
|
|
nCellVal, rItem.mfVal))
|
|
{
|
|
nRes = -1;
|
|
if (bAscending)
|
|
{
|
|
if (fLastInRangeValue <= nCellVal)
|
|
{
|
|
fLastInRangeValue = nCellVal;
|
|
nLastInRange = i;
|
|
}
|
|
else if (fLastInRangeValue >= nCellVal)
|
|
{
|
|
// not strictly sorted, continue with GetThis()
|
|
orderBroken = true;
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
else if ((nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(
|
|
nCellVal, rItem.mfVal))
|
|
{
|
|
nRes = 1;
|
|
if (!bAscending)
|
|
{
|
|
if (fLastInRangeValue >= nCellVal)
|
|
{
|
|
fLastInRangeValue = nCellVal;
|
|
nLastInRange = i;
|
|
}
|
|
else if (fLastInRangeValue <= nCellVal)
|
|
{
|
|
// not strictly sorted, continue with GetThis()
|
|
orderBroken = true;
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (bStr && bByString)
|
|
{
|
|
if (!maParam.bByRow)
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[aCellData.second];
|
|
sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, maParam.bByRow ? aCellData.second : nRow);
|
|
OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, &mrContext, rDoc);
|
|
|
|
nRes = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
|
|
if (nRes < 0 && bAscending)
|
|
{
|
|
sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
|
|
aCellStr);
|
|
if (nTmp <= 0)
|
|
{
|
|
aLastInRangeString = aCellStr;
|
|
nLastInRange = i;
|
|
}
|
|
else if (nTmp > 0)
|
|
{
|
|
// not strictly sorted, continue with GetThis()
|
|
orderBroken = true;
|
|
bDone = true;
|
|
}
|
|
}
|
|
else if (nRes > 0 && !bAscending)
|
|
{
|
|
sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
|
|
aCellStr);
|
|
if (nTmp >= 0)
|
|
{
|
|
aLastInRangeString = aCellStr;
|
|
nLastInRange = i;
|
|
}
|
|
else if (nTmp < 0)
|
|
{
|
|
// not strictly sorted, continue with GetThis()
|
|
orderBroken = true;
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
else if (!bStr && bByString)
|
|
{
|
|
nRes = -1; // numeric < string
|
|
if (bAscending)
|
|
nLastInRange = i;
|
|
}
|
|
else // if (bStr && !bByString)
|
|
{
|
|
nRes = 1; // string > numeric
|
|
if (!bAscending)
|
|
nLastInRange = i;
|
|
}
|
|
if (nRes < 0)
|
|
{
|
|
if (bAscending)
|
|
nLo = nMid + 1;
|
|
else // assumed to be SC_GREATER_EQUAL
|
|
{
|
|
if (nMid > 0)
|
|
nHi = nMid - 1;
|
|
else
|
|
bDone = true;
|
|
}
|
|
}
|
|
else if (nRes > 0)
|
|
{
|
|
if (bAscending)
|
|
{
|
|
if (nMid > 0)
|
|
nHi = nMid - 1;
|
|
else
|
|
bDone = true;
|
|
}
|
|
else // assumed to be SC_GREATER_EQUAL
|
|
nLo = nMid + 1;
|
|
}
|
|
else
|
|
{
|
|
if(rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL || rEntry.eOp == SC_EQUAL)
|
|
{
|
|
found = i;
|
|
nLastInRange = i;
|
|
nLo = nMid + 1;
|
|
}
|
|
else if (bAscending)
|
|
{
|
|
if (nMid > 0)
|
|
nHi = nMid - 1;
|
|
else
|
|
bDone = true;
|
|
}
|
|
else
|
|
{
|
|
if (nMid > 0)
|
|
nHi = nMid - 1;
|
|
else
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isInRange;
|
|
if (orderBroken)
|
|
{
|
|
// Reset position to the first row in range and force caller
|
|
// to search from start.
|
|
nLo = aIndexer.getLowIndex();
|
|
isInRange = false;
|
|
}
|
|
else if (found)
|
|
{
|
|
nLo = *found;
|
|
isInRange = true;
|
|
}
|
|
else
|
|
{
|
|
// Not nothing was found and the search position is at the start,
|
|
// then the possible match would need to be before the data range.
|
|
// In that case return false to force the caller to search from the start
|
|
// and detect this.
|
|
isInRange = nLo != aIndexer.getLowIndex();
|
|
// If nothing was found, that is either because there is no value
|
|
// that would match exactly, or the data range is not properly sorted
|
|
// and we failed to detect (doing so reliably would require a linear scan).
|
|
// Set the position to the last one that was in matching range (i.e. before
|
|
// where the exact match would be), and leave sorting it out to GetThis()
|
|
// or whatever the caller uses.
|
|
nLo = nLastInRange;
|
|
}
|
|
|
|
if (maParam.bByRow)
|
|
{
|
|
aCellData = aIndexer.getCell(nLo);
|
|
if (nLo <= nHi && aCellData.second <= maParam.nRow2)
|
|
{
|
|
nRow = aCellData.second;
|
|
maCurPos = aIndexer.getPosition(nLo);
|
|
return isInRange;
|
|
}
|
|
else
|
|
{
|
|
nRow = maParam.nRow2 + 1;
|
|
// Set current position to the last possible row.
|
|
maCurPos.first = pCol->maCells.end();
|
|
--maCurPos.first;
|
|
maCurPos.second = maCurPos.first->size - 1;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aCellData = aIndexer.getColCell(nLo, nRow);
|
|
if (nLo <= nHi && aCellData.second <= maParam.nCol2)
|
|
{
|
|
nCol = aCellData.second;
|
|
maCurPos = aIndexer.getColPosition(nLo, nRow);
|
|
return isInRange;
|
|
}
|
|
else
|
|
{
|
|
nCol = maParam.nCol2 + 1;
|
|
// Set current position to the last possible col.
|
|
pCol = &(rDoc.maTabs[nTab])->aCol[maParam.nCol2];
|
|
maCurPos.first = pCol->maCells.end();
|
|
--maCurPos.first;
|
|
maCurPos.second = maCurPos.first->size - 1;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template< ScQueryCellIteratorAccess accessType >
|
|
bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFoundCol,
|
|
SCROW& nFoundRow )
|
|
{
|
|
// Set and automatically reset mpParam->mbRangeLookup when returning.
|
|
comphelper::FlagRestorationGuard aRangeLookupResetter( maParam.mbRangeLookup, true );
|
|
|
|
nFoundCol = rDoc.MaxCol()+1;
|
|
nFoundRow = rDoc.MaxRow()+1;
|
|
|
|
if ((nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH) &&
|
|
nSortedBinarySearch == nBinarySearchDisabled)
|
|
SetStopOnMismatch( false ); // assume not sorted keys for XLookup/XMatch
|
|
else
|
|
SetStopOnMismatch( true ); // assume sorted keys
|
|
|
|
SetTestEqualCondition( true );
|
|
bIgnoreMismatchOnLeadingStrings = true;
|
|
|
|
bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal &&
|
|
maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString;
|
|
bool bBinary = maParam.bByRow &&
|
|
(bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) &&
|
|
(maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL);
|
|
|
|
// assume not sorted properly if we are using XLookup/XMatch with forward or backward search
|
|
if (bBinary && (nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH) &&
|
|
nSortedBinarySearch == nBinarySearchDisabled)
|
|
bBinary = false;
|
|
|
|
bool bFound = false;
|
|
if (bBinary)
|
|
{
|
|
if (BinarySearch( maParam.nCol1 ))
|
|
{
|
|
// BinarySearch() already positions correctly and only needs real
|
|
// query comparisons afterwards, skip the verification check below.
|
|
maParam.mbRangeLookup = false;
|
|
bFound = GetThis();
|
|
}
|
|
else // Not sorted properly, or before the range (in which case GetFirst() will be simple).
|
|
bFound = GetFirst();
|
|
}
|
|
else
|
|
{
|
|
bFound = GetFirst();
|
|
}
|
|
if (bFound)
|
|
{
|
|
// First equal entry or last smaller than (greater than) entry.
|
|
PositionType aPosSave;
|
|
bool bNext = false;
|
|
SCSIZE nEntries = maParam.GetEntryCount();
|
|
std::vector<SCCOL> aFoundFieldPositions(nEntries);
|
|
do
|
|
{
|
|
nFoundCol = GetCol();
|
|
nFoundRow = GetRow();
|
|
aPosSave = maCurPos;
|
|
// If we might need to rewind below, save the position to rewind to
|
|
// rather than calculate it as a diff between nCol and nFoundCol as
|
|
// PerformQuery can return early if nCol is greater than
|
|
// maParam.nCol2 or AllocatedColumns
|
|
if (maParam.mbRangeLookup && bAdvanceQuery)
|
|
{
|
|
for (SCSIZE j=0; j < nEntries; ++j)
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if (rEntry.bDoQuery)
|
|
aFoundFieldPositions[j] = maParam.GetEntry(j).nField;
|
|
else
|
|
break; // for
|
|
}
|
|
}
|
|
if (IsEqualConditionFulfilled())
|
|
break;
|
|
bNext = GetNext();
|
|
}
|
|
while (bNext);
|
|
|
|
// There may be no pNext but equal condition fulfilled if regular
|
|
// expressions are involved. Keep the found entry and proceed.
|
|
if (!bNext && !IsEqualConditionFulfilled())
|
|
{
|
|
// Step back to last in range and adjust position markers for
|
|
// GetNumberFormat() or similar.
|
|
bool bColDiff = nCol != nFoundCol;
|
|
nCol = nFoundCol;
|
|
nRow = nFoundRow;
|
|
maCurPos = std::move(aPosSave);
|
|
if (maParam.mbRangeLookup)
|
|
{
|
|
// Verify that the found entry does not only fulfill the range
|
|
// lookup but also the real query, i.e. not numeric was found
|
|
// if query is ByString and vice versa.
|
|
maParam.mbRangeLookup = false;
|
|
// Step back the last field advance if GetNext() did one.
|
|
if (bAdvanceQuery && bColDiff)
|
|
{
|
|
for (SCSIZE j=0; j < nEntries; ++j)
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if (rEntry.bDoQuery)
|
|
{
|
|
rEntry.nField = aFoundFieldPositions[j];
|
|
assert(rEntry.nField >= 0);
|
|
}
|
|
else
|
|
break; // for
|
|
}
|
|
}
|
|
// Check it.
|
|
if (!GetThis())
|
|
{
|
|
nFoundCol = rDoc.MaxCol()+1;
|
|
nFoundRow = rDoc.MaxRow()+1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (IsEqualConditionFulfilled() && (nSearchOpCode != SC_OPCODE_X_LOOKUP &&
|
|
nSearchOpCode != SC_OPCODE_X_MATCH))
|
|
{
|
|
// Position on last equal entry, except for XLOOKUP,
|
|
// which looking for the first equal entry
|
|
SCSIZE nEntries = maParam.GetEntryCount();
|
|
for ( SCSIZE j = 0; j < nEntries; j++ )
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if ( rEntry.bDoQuery )
|
|
{
|
|
switch ( rEntry.eOp )
|
|
{
|
|
case SC_LESS_EQUAL :
|
|
case SC_GREATER_EQUAL :
|
|
rEntry.eOp = SC_EQUAL;
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
}
|
|
else
|
|
break; // for
|
|
}
|
|
PositionType aPosSave;
|
|
bIgnoreMismatchOnLeadingStrings = false;
|
|
SetTestEqualCondition( false );
|
|
do
|
|
{
|
|
nFoundCol = GetCol();
|
|
nFoundRow = GetRow();
|
|
aPosSave = maCurPos;
|
|
} while (GetNext());
|
|
|
|
// Step back conditions are the same as above
|
|
nCol = nFoundCol;
|
|
nRow = nFoundRow;
|
|
maCurPos = std::move(aPosSave);
|
|
return true;
|
|
}
|
|
if ( (maParam.eSearchType != utl::SearchParam::SearchType::Normal) &&
|
|
StoppedOnMismatch() )
|
|
{
|
|
// Assume found entry to be the last value less than respectively
|
|
// greater than the query. But keep on searching for an equal match.
|
|
SCSIZE nEntries = maParam.GetEntryCount();
|
|
for ( SCSIZE j = 0; j < nEntries; j++ )
|
|
{
|
|
ScQueryEntry& rEntry = maParam.GetEntry( j );
|
|
if ( rEntry.bDoQuery )
|
|
{
|
|
switch ( rEntry.eOp )
|
|
{
|
|
case SC_LESS_EQUAL :
|
|
case SC_GREATER_EQUAL :
|
|
rEntry.eOp = SC_EQUAL;
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
}
|
|
else
|
|
break; // for
|
|
}
|
|
SetStopOnMismatch( false );
|
|
SetTestEqualCondition( false );
|
|
if (GetNext())
|
|
{
|
|
// Last of a consecutive area, avoid searching the entire parameter
|
|
// range as it is a real performance bottleneck in case of regular
|
|
// expressions.
|
|
PositionType aPosSave;
|
|
do
|
|
{
|
|
nFoundCol = GetCol();
|
|
nFoundRow = GetRow();
|
|
aPosSave = maCurPos;
|
|
SetStopOnMismatch( true );
|
|
} while (GetNext());
|
|
nCol = nFoundCol;
|
|
nRow = nFoundRow;
|
|
maCurPos = std::move(aPosSave);
|
|
}
|
|
}
|
|
return (nFoundCol <= rDoc.MaxCol()) && (nFoundRow <= rDoc.MaxRow());
|
|
}
|
|
|
|
// Direct linear cell access using mdds.
|
|
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >
|
|
::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
|
|
ScInterpreterContext& rContext, const ScQueryParam& rParam, bool bReverseSearch )
|
|
: maParam( rParam )
|
|
, rDoc( rDocument )
|
|
, mrContext( rContext )
|
|
, mbReverseSearch( bReverseSearch )
|
|
{
|
|
// coverity[uninit_member] - this just contains data, subclass will initialize some of it
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::InitPos()
|
|
{
|
|
if (!mbReverseSearch)
|
|
{
|
|
nRow = maParam.nRow1;
|
|
if (maParam.bHasHeader && maParam.bByRow)
|
|
++nRow;
|
|
}
|
|
else
|
|
{
|
|
nRow = maParam.nRow2;
|
|
}
|
|
const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
|
|
maCurPos = rCol.maCells.position(nRow);
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncPos()
|
|
{
|
|
if (maCurPos.second + 1 < maCurPos.first->size)
|
|
{
|
|
// Move within the same block.
|
|
++maCurPos.second;
|
|
++nRow;
|
|
}
|
|
else
|
|
// Move to the next block.
|
|
IncBlock();
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::DecPos()
|
|
{
|
|
if (maCurPos.second > 0)
|
|
{
|
|
// Move within the same block.
|
|
--maCurPos.second;
|
|
--nRow;
|
|
}
|
|
else
|
|
// Move to the prev block.
|
|
DecBlock();
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncBlock()
|
|
{
|
|
++maCurPos.first;
|
|
maCurPos.second = 0;
|
|
|
|
nRow = maCurPos.first->position;
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::DecBlock()
|
|
{
|
|
// Set current position to the last possible row.
|
|
const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
|
|
if (maCurPos.first != rCol.maCells.begin())
|
|
{
|
|
--maCurPos.first;
|
|
maCurPos.second = maCurPos.first->size - 1;
|
|
|
|
nRow = maCurPos.first->position + maCurPos.second;
|
|
}
|
|
else
|
|
{
|
|
// No rows, set to end. This will make PerformQuery() go to next column.
|
|
nRow = maParam.nRow1 - 1;
|
|
maCurPos.first = rCol.maCells.end();
|
|
maCurPos.second = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class sequentially indexes non-empty cells in order, from the top of
|
|
* the block where the start row position is, to the bottom of the block
|
|
* where the end row position is. It skips all empty blocks that may be
|
|
* present in between.
|
|
*
|
|
* The index value is an offset from the first element of the first block
|
|
* disregarding all empty cell blocks.
|
|
*/
|
|
class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
|
|
{
|
|
typedef std::map<size_t, sc::CellStoreType::const_iterator> BlockMapType;
|
|
|
|
BlockMapType maBlockMap;
|
|
|
|
const sc::CellStoreType* mpCells;
|
|
|
|
size_t mnLowIndex;
|
|
size_t mnHighIndex;
|
|
|
|
bool mbValid;
|
|
|
|
public:
|
|
/**
|
|
* @param rCells cell storage container
|
|
* @param nStartRow logical start row position
|
|
* @param nEndRow logical end row position, inclusive.
|
|
*/
|
|
NonEmptyCellIndexer(
|
|
const sc::CellStoreType* pCells, SCROW nStartRow, SCROW nEndRow) :
|
|
mpCells(pCells), mnLowIndex(0), mnHighIndex(0), mbValid(true)
|
|
{
|
|
// Find the low position.
|
|
|
|
sc::CellStoreType::const_position_type aLoPos = mpCells->position(nStartRow);
|
|
assert(aLoPos.first->type != sc::element_type_empty);
|
|
assert(aLoPos.first != mpCells->end());
|
|
|
|
SCROW nFirstRow = aLoPos.first->position;
|
|
SCROW nLastRow = aLoPos.first->position + aLoPos.first->size - 1;
|
|
|
|
if (nFirstRow > nEndRow)
|
|
{
|
|
// Both start and end row positions are within the leading skipped
|
|
// blocks.
|
|
mbValid = false;
|
|
return;
|
|
}
|
|
|
|
// Calculate the index of the low position.
|
|
if (nFirstRow < nStartRow)
|
|
mnLowIndex = nStartRow - nFirstRow;
|
|
else
|
|
{
|
|
// Start row is within the skipped block(s). Set it to the first
|
|
// element of the low block.
|
|
mnLowIndex = 0;
|
|
}
|
|
|
|
if (nEndRow < nLastRow)
|
|
{
|
|
assert(nEndRow >= nFirstRow);
|
|
mnHighIndex = nEndRow - nFirstRow;
|
|
|
|
maBlockMap.emplace(aLoPos.first->size, aLoPos.first);
|
|
return;
|
|
}
|
|
|
|
// Find the high position.
|
|
|
|
sc::CellStoreType::const_position_type aHiPos = mpCells->position(aLoPos.first, nEndRow);
|
|
if (aHiPos.first->type == sc::element_type_empty)
|
|
{
|
|
// Move to the last position of the previous block.
|
|
decBlock(aHiPos);
|
|
|
|
// Check the row position of the end of the previous block, and make sure it's valid.
|
|
SCROW nBlockEndRow = aHiPos.first->position + aHiPos.first->size - 1;
|
|
if (nBlockEndRow < nStartRow)
|
|
{
|
|
mbValid = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Tag the start and end blocks, and all blocks in between in order
|
|
// but skip all empty blocks.
|
|
|
|
size_t nPos = 0;
|
|
sc::CellStoreType::const_iterator itBlk = aLoPos.first;
|
|
while (itBlk != aHiPos.first)
|
|
{
|
|
if (itBlk->type == sc::element_type_empty)
|
|
{
|
|
++itBlk;
|
|
continue;
|
|
}
|
|
|
|
nPos += itBlk->size;
|
|
maBlockMap.emplace(nPos, itBlk);
|
|
++itBlk;
|
|
|
|
if (itBlk->type == sc::element_type_empty)
|
|
++itBlk;
|
|
|
|
assert(itBlk != mpCells->end());
|
|
}
|
|
|
|
assert(itBlk == aHiPos.first);
|
|
nPos += itBlk->size;
|
|
maBlockMap.emplace(nPos, itBlk);
|
|
|
|
// Calculate the high index.
|
|
BlockMapType::const_reverse_iterator ri = maBlockMap.rbegin();
|
|
mnHighIndex = ri->first;
|
|
mnHighIndex -= ri->second->size;
|
|
mnHighIndex += aHiPos.second;
|
|
}
|
|
|
|
sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
|
|
{
|
|
assert(mbValid);
|
|
assert(mnLowIndex <= nIndex);
|
|
assert(nIndex <= mnHighIndex);
|
|
|
|
sc::CellStoreType::const_position_type aRet(mpCells->end(), 0);
|
|
|
|
BlockMapType::const_iterator it = maBlockMap.upper_bound(nIndex);
|
|
if (it == maBlockMap.end())
|
|
return aRet;
|
|
|
|
sc::CellStoreType::const_iterator itBlk = it->second;
|
|
size_t nBlkIndex = it->first - itBlk->size; // index of the first element of the block.
|
|
assert(nBlkIndex <= nIndex);
|
|
assert(nIndex < it->first);
|
|
|
|
size_t nOffset = nIndex - nBlkIndex;
|
|
aRet.first = std::move(itBlk);
|
|
aRet.second = nOffset;
|
|
return aRet;
|
|
}
|
|
|
|
BinarySearchCellType getCell( size_t nIndex ) const
|
|
{
|
|
BinarySearchCellType aRet;
|
|
aRet.second = -1;
|
|
|
|
sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
|
|
if (aPos.first == mpCells->end())
|
|
return aRet;
|
|
|
|
aRet.first = sc::toRefCell(aPos.first, aPos.second);
|
|
aRet.second = aPos.first->position + aPos.second;
|
|
return aRet;
|
|
}
|
|
|
|
// TODO: NonEmptyCellIndexer for columns search
|
|
static sc::CellStoreType::const_position_type getColPosition(size_t /*nIndex*/, SCROW /*nRow*/)
|
|
{
|
|
return sc::CellStoreType::const_position_type();
|
|
}
|
|
|
|
// TODO: NonEmptyCellIndexer for columns search
|
|
static BinarySearchCellType getColCell(size_t /*nIndex*/, SCROW /*nRow*/)
|
|
{
|
|
BinarySearchCellType aRet;
|
|
return aRet;
|
|
}
|
|
|
|
size_t getLowIndex() const { return mnLowIndex; }
|
|
|
|
size_t getHighIndex() const { return mnHighIndex; }
|
|
|
|
bool isValid() const { return mbValid; }
|
|
};
|
|
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::MakeBinarySearchIndexer(
|
|
const sc::CellStoreType* pCells, SCCOLROW nStartRow, SCCOLROW nEndRow)
|
|
{
|
|
return NonEmptyCellIndexer(pCells, nStartRow, nEndRow);
|
|
}
|
|
|
|
// Sorted access using ScSortedRangeCache.
|
|
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
|
|
::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
|
|
ScInterpreterContext& rContext, const ScQueryParam& rParam, bool bReverseSearch )
|
|
: maParam( rParam )
|
|
, rDoc( rDocument )
|
|
, mrContext( rContext )
|
|
, mbReverseSearch( bReverseSearch )
|
|
{
|
|
// coverity[uninit_member] - this just contains data, subclass will initialize some of it
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SetSortedRangeCache(
|
|
const ScSortedRangeCache& cache)
|
|
{
|
|
sortedCache = &cache;
|
|
}
|
|
|
|
// The idea in iterating using the sorted cache is that the iteration is instead done
|
|
// over indexes of the sorted cache (which is a stable sort of the cell contents) in the range
|
|
// that fits the query condition and then that is mapped to rows. This will result in iterating
|
|
// over only matching rows in their sorted order (and for equal rows in their row order).
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosStart(bool bNewSearchFunction, sal_uInt8 nSortedBinarySearch)
|
|
{
|
|
ScRange aSortedRangeRange( maParam.nCol1, maParam.nRow1, nTab, maParam.nCol2, maParam.nRow2, nTab );
|
|
// We want all matching values first in the sort order,
|
|
SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext, bNewSearchFunction, nSortedBinarySearch ));
|
|
// InitPosFinish() needs to be called after this, ScQueryCellIteratorBase::InitPos()
|
|
// will handle that
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosFinish(
|
|
SCROW beforeRow, SCROW lastRow, bool bFirstMatch )
|
|
{
|
|
pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
|
|
if(lastRow >= 0)
|
|
{
|
|
sortedCachePos = beforeRow >= 0 ? sortedCache->indexForRow(beforeRow) + 1 : 0;
|
|
sortedCachePosLast = sortedCache->indexForRow(lastRow);
|
|
if(sortedCachePos <= sortedCachePosLast)
|
|
{
|
|
if (!bFirstMatch)
|
|
nRow = sortedCache->rowForIndex(sortedCachePos);
|
|
else
|
|
nRow = sortedCache->rowForIndex(sortedCachePosLast);
|
|
maCurPos = pColumn->maCells.position(nRow);
|
|
return;
|
|
}
|
|
}
|
|
// No rows, set to end.
|
|
sortedCachePos = sortedCachePosLast = 0;
|
|
maCurPos.first = pColumn->maCells.end();
|
|
maCurPos.second = 0;
|
|
}
|
|
|
|
void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosColFinish(
|
|
SCCOL beforeCol, SCCOL lastCol, bool bFirstMatch)
|
|
{
|
|
pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
|
|
if (lastCol >= 0)
|
|
{
|
|
sortedCachePos = beforeCol >= 0 ? sortedCache->indexForCol(beforeCol) + 1 : 0;
|
|
sortedCachePosLast = sortedCache->indexForCol(lastCol);
|
|
if (sortedCachePos <= sortedCachePosLast)
|
|
{
|
|
if (!bFirstMatch)
|
|
nCol = sortedCache->colForIndex(sortedCachePos);
|
|
else
|
|
nCol = sortedCache->colForIndex(sortedCachePosLast);
|
|
pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
|
|
maCurPos = pColumn->maCells.position(nRow);
|
|
return;
|
|
}
|
|
}
|
|
// No cols, set to end.
|
|
sortedCachePos = sortedCachePosLast = 0;
|
|
maCurPos.first = pColumn->maCells.end();
|
|
maCurPos.second = 0;
|
|
}
|
|
|
|
template<bool fast>
|
|
bool ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::IncPosImpl()
|
|
{
|
|
if(sortedCachePos < sortedCachePosLast)
|
|
{
|
|
++sortedCachePos;
|
|
if (maParam.bByRow)
|
|
nRow = sortedCache->rowForIndex(sortedCachePos);
|
|
else
|
|
nCol = sortedCache->colForIndex(sortedCachePos);
|
|
#ifndef DBG_UTIL
|
|
if constexpr (!fast)
|
|
#endif
|
|
{
|
|
if (maParam.bByRow)
|
|
{
|
|
// Avoid mdds position() call if row is in the same block.
|
|
if (maCurPos.first != pColumn->maCells.end() && o3tl::make_unsigned(nRow) >= maCurPos.first->position
|
|
&& o3tl::make_unsigned(nRow) < maCurPos.first->position + maCurPos.first->size)
|
|
maCurPos.second = nRow - maCurPos.first->position;
|
|
else
|
|
maCurPos = pColumn->maCells.position(nRow);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// This will make PerformQuery() go to next column.
|
|
// Necessary even in fast mode, as GetNext() will call GetThis() in this case.
|
|
if (!maParam.bByRow)
|
|
++nRow;
|
|
maCurPos.first = pColumn->maCells.end();
|
|
maCurPos.second = 0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template
|
|
bool ScQueryCellIteratorAccessSpecific<ScQueryCellIteratorAccess::SortedCache>::IncPosImpl<false>();
|
|
template
|
|
bool ScQueryCellIteratorAccessSpecific<ScQueryCellIteratorAccess::SortedCache>::IncPosImpl<true>();
|
|
|
|
// Helper that allows binary search of unsorted cells using ScSortedRangeCache.
|
|
// Rows in the given range are kept in a sorted vector and that vector is binary-searched.
|
|
class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
|
|
{
|
|
std::vector<SCCOLROW> mSortedColsRowsCopy;
|
|
const std::vector<SCCOLROW>& mSortedColsRows;
|
|
ScDocument& mrDoc;
|
|
const sc::CellStoreType* mpCells;
|
|
size_t mLowIndex;
|
|
size_t mHighIndex;
|
|
bool mValid;
|
|
SCTAB mnTab;
|
|
|
|
const std::vector<SCCOLROW>& makeSortedColsRows( const ScSortedRangeCache* cache, SCCOLROW startColRow, SCCOLROW endColRow )
|
|
{
|
|
// Keep a reference to cols/rows from the cache if equal, otherwise make a copy.
|
|
if (cache->isRowSearch())
|
|
{
|
|
if (startColRow == cache->getRange().aStart.Row() && endColRow == cache->getRange().aEnd.Row())
|
|
return cache->sortedRows();
|
|
else
|
|
{
|
|
mSortedColsRowsCopy.reserve(cache->sortedRows().size());
|
|
for (SCROW row : cache->sortedRows())
|
|
if (row >= startColRow && row <= endColRow)
|
|
mSortedColsRowsCopy.emplace_back(row);
|
|
return mSortedColsRowsCopy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (startColRow == cache->getRange().aStart.Col() && endColRow == cache->getRange().aEnd.Col())
|
|
return cache->sortedCols();
|
|
else
|
|
{
|
|
mSortedColsRowsCopy.reserve(cache->sortedCols().size());
|
|
for (SCCOL col : cache->sortedCols())
|
|
if (col >= startColRow && col <= endColRow)
|
|
mSortedColsRowsCopy.emplace_back(col);
|
|
return mSortedColsRowsCopy;
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
SortedCacheIndexer( ScDocument& rDoc, const sc::CellStoreType* cells,
|
|
SCCOLROW startColRow, SCCOLROW endColRow, SCTAB nTab,
|
|
const ScSortedRangeCache* cache )
|
|
: mSortedColsRows( makeSortedColsRows( cache, startColRow, endColRow))
|
|
, mrDoc( rDoc )
|
|
, mpCells( cells )
|
|
, mValid( false )
|
|
, mnTab( nTab )
|
|
{
|
|
if(mSortedColsRows.empty())
|
|
{
|
|
// coverity[uninit_member] - these are initialized only if valid
|
|
return;
|
|
}
|
|
mLowIndex = 0;
|
|
mHighIndex = mSortedColsRows.size() - 1;
|
|
mValid = true;
|
|
}
|
|
|
|
sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
|
|
{
|
|
// TODO optimize?
|
|
SCROW row = mSortedColsRows[ nIndex ];
|
|
return mpCells->position(row);
|
|
}
|
|
|
|
BinarySearchCellType getCell( size_t nIndex ) const
|
|
{
|
|
BinarySearchCellType aRet;
|
|
aRet.second = -1;
|
|
|
|
sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
|
|
if (aPos.first == mpCells->end())
|
|
return aRet;
|
|
|
|
aRet.first = sc::toRefCell(aPos.first, aPos.second);
|
|
aRet.second = aPos.first->position + aPos.second;
|
|
return aRet;
|
|
}
|
|
|
|
sc::CellStoreType::const_position_type getColPosition( size_t nColIndex, SCROW nRowPos ) const
|
|
{
|
|
// TODO optimize?
|
|
SCCOL col = mSortedColsRows[nColIndex];
|
|
if (col >= mrDoc.maTabs[mnTab]->GetAllocatedColumnsCount())
|
|
return sc::CellStoreType::const_position_type();
|
|
|
|
const ScColumn& rCol = mrDoc.maTabs[mnTab]->aCol[col];
|
|
return rCol.maCells.position(nRowPos);
|
|
}
|
|
|
|
BinarySearchCellType getColCell( size_t nColIndex, SCROW nRowPos) const
|
|
{
|
|
BinarySearchCellType aRet;
|
|
aRet.second = -1;
|
|
|
|
sc::CellStoreType::const_position_type aPos = getColPosition(nColIndex, nRowPos);
|
|
|
|
aRet.first = sc::toRefCell(aPos.first, aPos.second);
|
|
aRet.second = mSortedColsRows[nColIndex];
|
|
|
|
return aRet;
|
|
}
|
|
|
|
size_t getLowIndex() const { return mLowIndex; }
|
|
|
|
size_t getHighIndex() const { return mHighIndex; }
|
|
|
|
bool isValid() const { return mValid; }
|
|
};
|
|
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
|
|
ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::MakeBinarySearchIndexer(
|
|
const sc::CellStoreType* pCells, SCCOLROW nStartRow, SCCOLROW nEndRow)
|
|
{
|
|
return SortedCacheIndexer(rDoc, pCells, nStartRow, nEndRow, nTab, sortedCache);
|
|
}
|
|
|
|
static bool CanBeUsedForSorterCache(ScDocument& /*rDoc*/, const ScQueryParam& /*rParam*/,
|
|
SCTAB /*nTab*/, const ScFormulaCell* /*cell*/, const ScComplexRefData* /*refData*/,
|
|
ScInterpreterContext& /*context*/)
|
|
{
|
|
#if 1
|
|
/* TODO: tdf#151958 broken by string query of binary search on sorted
|
|
* cache, use the direct query instead for releases and fix SortedCache
|
|
* implementation after. Not only COUNTIF() is broken, but also COUNTIFS(),
|
|
* and maybe lcl_LookupQuery() for VLOOKUP() etc. as well. Just disable
|
|
* this for now.
|
|
* Can't just return false because below would be unreachable code. Can't
|
|
* just #if/#else/#endif either because parameters would be unused. Crap
|
|
* this and comment out parameter names. */
|
|
return false;
|
|
#else
|
|
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
|
|
&& rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByString)
|
|
return false;
|
|
if(!rParam.bByRow)
|
|
return false;
|
|
if(rParam.bHasHeader)
|
|
return false;
|
|
if(rParam.mbRangeLookup)
|
|
return false;
|
|
const bool bNewSearchFunction = nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH;
|
|
if(rParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString && !bNewSearchFunction
|
|
&& !ScQueryEvaluator::isMatchWholeCell(rDoc, rParam.GetEntry(0).eOp))
|
|
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)
|
|
return false;
|
|
// For unittests allow inefficient caching, in order for the code to be checked.
|
|
static const bool bRunningUnitTest = o3tl::IsRunningUnitTest();
|
|
if(refData == nullptr || refData->Ref1.IsRowRel() || refData->Ref2.IsRowRel())
|
|
{
|
|
// If this is not a range, then a cache is not worth it. If rows are relative, then each
|
|
// computation will use a different area, so the cache wouldn't be reused. Tab/cols are
|
|
// not a problem, because formula group computations are done for the same tab/col.
|
|
if(!bRunningUnitTest)
|
|
return false;
|
|
}
|
|
if(rParam.nRow2 - rParam.nRow1 < 10)
|
|
{
|
|
if(!bRunningUnitTest)
|
|
return false;
|
|
}
|
|
if( !cell )
|
|
return false;
|
|
if( !cell->GetCellGroup() || cell->GetCellGroup()->mnLength < 10 )
|
|
{
|
|
if(!bRunningUnitTest)
|
|
return false;
|
|
}
|
|
// Check that all the relevant caches would be valid (may not be the case when mixing
|
|
// numeric and string cells for ByValue lookups).
|
|
for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, rParam.nCol1, rParam.nCol2))
|
|
{
|
|
ScRange aSortedRangeRange( col, rParam.nRow1, nTab, col, rParam.nRow2, nTab);
|
|
if( aSortedRangeRange.Contains( cell->aPos ))
|
|
return false; // self-referencing, can't create cache
|
|
ScSortedRangeCache& cache = rDoc.GetSortedRangeCache( aSortedRangeRange, rParam, &context );
|
|
if(!cache.isValid())
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// Generic query implementation.
|
|
|
|
bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::Generic >::HandleItemFound()
|
|
{
|
|
getThisResult = true;
|
|
return true; // Return from PerformQuery().
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType >
|
|
bool ScQueryCellIterator< accessType >::GetThis()
|
|
{
|
|
getThisResult = false;
|
|
PerformQuery();
|
|
return getThisResult;
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType >
|
|
bool ScQueryCellIterator< accessType >::GetFirst()
|
|
{
|
|
assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
|
|
if (!mbReverseSearch)
|
|
nCol = maParam.nCol1;
|
|
else
|
|
nCol = maParam.nCol2;
|
|
InitPos();
|
|
return GetThis();
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType >
|
|
bool ScQueryCellIterator< accessType >::GetNext()
|
|
{
|
|
if (!mbReverseSearch)
|
|
IncPos();
|
|
else
|
|
DecPos();
|
|
if ( nStopOnMismatch )
|
|
nStopOnMismatch = nStopOnMismatchEnabled;
|
|
if ( nTestEqualCondition )
|
|
nTestEqualCondition = nTestEqualConditionEnabled;
|
|
return GetThis();
|
|
}
|
|
|
|
template<>
|
|
bool ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetNext()
|
|
{
|
|
assert( !nStopOnMismatch );
|
|
assert( !nTestEqualCondition );
|
|
// When searching using sorted cache, we should always find cells that match,
|
|
// because InitPos()/IncPos() select only such rows, so skip GetThis() (and thus
|
|
// the somewhat expensive PerformQuery) as long as we're not at the end
|
|
// of a column. As an optimization IncPosFast() returns true if not at the end,
|
|
// in which case in non-DBG_UTIL mode it doesn't even bother to set maCurPos.
|
|
if( IncPosFast())
|
|
{
|
|
#ifdef DBG_UTIL
|
|
assert(GetThis());
|
|
#endif
|
|
return true;
|
|
}
|
|
return GetThis();
|
|
}
|
|
|
|
bool ScQueryCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
|
|
SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
|
|
ScInterpreterContext& context)
|
|
{
|
|
return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
|
|
}
|
|
|
|
// Countifs implementation.
|
|
|
|
bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::CountIf >::HandleItemFound()
|
|
{
|
|
++countIfCount;
|
|
return false; // Continue searching.
|
|
}
|
|
|
|
template< ScQueryCellIteratorAccess accessType >
|
|
sal_uInt64 ScCountIfCellIterator< accessType >::GetCount()
|
|
{
|
|
// Keep Entry.nField in iterator on column change
|
|
SetAdvanceQueryParamEntryField( true );
|
|
assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
|
|
maParam.nCol1 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol1);
|
|
maParam.nCol2 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol2);
|
|
nCol = maParam.nCol1;
|
|
InitPos();
|
|
countIfCount = 0;
|
|
PerformQuery();
|
|
return countIfCount;
|
|
}
|
|
|
|
|
|
bool ScCountIfCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
|
|
SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
|
|
ScInterpreterContext& context)
|
|
{
|
|
return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
|
|
}
|
|
|
|
template<>
|
|
sal_uInt64 ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetCount()
|
|
{
|
|
// Keep Entry.nField in iterator on column change
|
|
SetAdvanceQueryParamEntryField( true );
|
|
assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
|
|
sal_uInt64 count = 0;
|
|
// Each column must be sorted separately.
|
|
for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, maParam.nCol1, maParam.nCol2))
|
|
{
|
|
nCol = col;
|
|
nRow = maParam.nRow1;
|
|
ScRange aSortedRangeRange( col, maParam.nRow1, nTab, col, maParam.nRow2, nTab);
|
|
ScQueryOp& op = maParam.GetEntry(0).eOp;
|
|
bool bNewSearchFunction = nSearchOpCode == SC_OPCODE_X_LOOKUP || nSearchOpCode == SC_OPCODE_X_MATCH;
|
|
SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext, bNewSearchFunction, nSearchOpCode ));
|
|
if( op == SC_EQUAL )
|
|
{
|
|
// BinarySearch() searches for the last item that matches. Therefore first
|
|
// find the last non-matching position using SC_LESS and then find the last
|
|
// matching position using SC_EQUAL.
|
|
ScQueryOp saveOp = op;
|
|
op = SC_LESS;
|
|
if( BinarySearch( nCol, true ))
|
|
{
|
|
op = saveOp; // back to SC_EQUAL
|
|
size_t lastNonMatching = sortedCache->indexForRow(nRow);
|
|
if( BinarySearch( nCol ))
|
|
{
|
|
size_t lastMatching = sortedCache->indexForRow(nRow);
|
|
assert(lastMatching >= lastNonMatching);
|
|
count += lastMatching - lastNonMatching;
|
|
}
|
|
else
|
|
{
|
|
// BinarySearch() should at least find the same result as the SC_LESS
|
|
// call, so this should not happen.
|
|
assert(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// BinarySearch() returning false means that all values are larger,
|
|
// so try to find matching ones and count those up to and including
|
|
// the found one.
|
|
op = saveOp; // back to SC_EQUAL
|
|
if( BinarySearch( nCol ))
|
|
{
|
|
size_t lastMatching = sortedCache->indexForRow(nRow) + 1;
|
|
count += lastMatching;
|
|
}
|
|
else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
|
|
&& rDoc.IsEmptyData(col, maParam.nRow1, col, maParam.nRow2, nTab))
|
|
{
|
|
// BinarySearch() returns false in case it's all empty data,
|
|
// handle that specially.
|
|
count += maParam.nRow2 - maParam.nRow1 + 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// BinarySearch() searches for the last item that matches. Therefore everything
|
|
// up to and including the found row matches the condition.
|
|
if( BinarySearch( nCol ))
|
|
count += sortedCache->indexForRow(nRow) + 1;
|
|
}
|
|
}
|
|
if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
|
|
&& maParam.nCol2 >= rDoc.GetAllocatedColumnsCount( nTab ))
|
|
{
|
|
const sal_uInt64 nRows = maParam.nRow2 - maParam.nRow1 + 1;
|
|
count += (maParam.nCol2 - rDoc.GetAllocatedColumnsCount(nTab)) * nRows;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
template class ScQueryCellIterator< ScQueryCellIteratorAccess::Direct >;
|
|
template class ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >;
|
|
template class ScCountIfCellIterator< ScQueryCellIteratorAccess::Direct >;
|
|
template class ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >;
|
|
|
|
// gcc for some reason needs these too
|
|
template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::Generic >;
|
|
template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::Generic >;
|
|
template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::CountIf >;
|
|
template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::CountIf >;
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|