/* -*- 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 .
*/
// TODO : Threaded pivot cache operation is disabled until we can figure out // ways to make the edit engine and number formatter codes thread-safe in a // proper fashion. #define ENABLE_THREADED_PIVOT_CACHE 0
ScDPCache::~ScDPCache()
{ // Make sure no live ScDPObject instances hold reference to this cache any // more.
mbDisposing = true;
std::for_each(maRefObjects.begin(), maRefObjects.end(), ClearObjectSource());
}
namespace {
/** * While the macro interpret level is incremented, the formula cells are * (semi-)guaranteed to be interpreted.
*/ class MacroInterpretIncrementer
{ public: explicit MacroInterpretIncrementer(ScDocument& rDoc) :
mrDoc(rDoc)
{
mrDoc.IncMacroInterpretLevel();
}
~MacroInterpretIncrementer()
{
mrDoc.DecMacroInterpretLevel();
} private:
ScDocument& mrDoc;
};
void processBuckets(std::vector<Bucket>& aBuckets, ScDPCache::Field& rField)
{ if (aBuckets.empty()) return;
// Sort by the value.
comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByValue());
{ // Set order index such that unique values have identical index value.
SCROW nCurIndex = 0;
std::vector<Bucket>::iterator it = aBuckets.begin(), itEnd = aBuckets.end();
ScDPItemData aPrev = it->maValue;
it->mnOrderIndex = nCurIndex; for (++it; it != itEnd; ++it)
{ if (!aPrev.IsCaseInsEqual(it->maValue))
++nCurIndex;
// Re-sort the bucket this time by the data index.
comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByDataIndex());
// Copy the order index series into the field object.
rField.maData.reserve(aBuckets.size());
std::for_each(aBuckets.begin(), aBuckets.end(), PushBackOrderIndex(rField.maData));
// Sort by the value again.
comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByOrderIndex());
// Unique by value.
std::vector<Bucket>::iterator itUniqueEnd =
std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex());
if (!aData.IsEmpty())
{
rColData.maEmptyRows.insert_back(i, i+1, false); if (nNumFormat) // Only take non-default number format.
rField.mnNumFormat = nNumFormat;
}
}
processBuckets(aBuckets, rField);
if (bTailEmptyRows)
{ // If the last item is not empty, append one. Note that the items // are sorted, and empty item should come last when sorted. if (rField.maItems.empty() || !rField.maItems.back().IsEmpty())
{
aData.SetEmpty();
rField.maItems.push_back(aData);
}
}
}
#if ENABLE_THREADED_PIVOT_CACHE
class ThreadQueue
{ using FutureType = std::future<void>;
std::queue<FutureType> maQueue;
std::mutex maMutex;
std::condition_variable maCond;
// Make sure the formula cells within the data range are interpreted // during this call, for this method may be called from the interpretation // of GETPIVOTDATA, which disables nested formula interpretation without // increasing the macro level.
MacroInterpretIncrementer aMacroInc(rDoc);
aDocData.mnStartRow = rRange.aStart.Row(); // start of data
aDocData.mnEndRow = rRange.aEnd.Row();
if (aDocData.mnEndRow <= aDocData.mnStartRow)
{ // Check this again since the end row position has changed. It's // possible that the new end row becomes lower than the start row // after the shrinkage.
Clear(); return;
}
maStringPools.resize(mnColumnCount);
std::vector<InitColumnData> aColData(mnColumnCount, InitColumnData(rDoc.GetSheetLimits()));
maFields.reserve(mnColumnCount); for (SCCOL i = 0; i < mnColumnCount; ++i)
maFields.push_back(std::make_unique<Field>());
maLabelNames.reserve(mnColumnCount+1);
// Ensure that none of the formula cells in the data range are dirty.
rDoc.EnsureFormulaCellResults(rRange);
auto func = [&aDocData,&rColData]()
{
initColumnFromDoc(aDocData, rColData);
};
aQueue.push(std::move(func));
}
};
{ // Launch a separate thread that in turn spawns async threads to populate the fields.
std::thread t(aFuncLaunchFieldThreads);
ThreadScopedGuard sg(std::move(t));
// Wait for all the async threads to complete on the main thread. for (SCCOL i = 0; i < mnColumnCount; ++i)
aQueue.waitForOne();
}
for (size_t i = 0; i < nEntryCount && rParam.GetEntry(i).bDoQuery; ++i)
{ const ScQueryEntry& rEntry = rParam.GetEntry(i); const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); // we can only handle one single direct query // #i115431# nField in QueryParam is the sheet column, not the field within the source range
SCCOL nQueryCol = static_cast<SCCOL>(rEntry.nField); if ( nQueryCol < rParam.nCol1 )
nQueryCol = rParam.nCol1; if ( nQueryCol > rParam.nCol2 )
nQueryCol = rParam.nCol2;
SCCOL nSourceField = nQueryCol - rParam.nCol1;
SCROW nId = GetItemDataId( nSourceField, nRow, false ); const ScDPItemData* pCellData = GetItemDataById( nSourceField, nId );
bool bOk = false;
if (rEntry.GetQueryItem().meType == ScQueryEntry::ByEmpty)
{ if (rEntry.IsQueryByEmpty())
bOk = pCellData->IsEmpty(); else
{
assert(rEntry.IsQueryByNonEmpty());
bOk = !pCellData->IsEmpty();
}
} elseif (rEntry.GetQueryItem().meType != ScQueryEntry::ByString && pCellData->IsValue())
{ // by Value double nCellVal = pCellData->GetValue();
switch (rEntry.eOp)
{ case SC_EQUAL :
bOk = ::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; case SC_LESS :
bOk = (nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; case SC_GREATER :
bOk = (nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; case SC_LESS_EQUAL :
bOk = (nCellVal < rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; case SC_GREATER_EQUAL :
bOk = (nCellVal > rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; case SC_NOT_EQUAL :
bOk = !::rtl::math::approxEqual(nCellVal, rItem.mfVal); break; default:
bOk= false; break;
}
} elseif ((rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
|| (rEntry.GetQueryItem().meType == ScQueryEntry::ByString
&& pCellData->HasStringData() )
)
{ // by String
OUString aCellStr = pCellData->GetString();
bool bMatch = rEntry.GetSearchTextPtr( rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell )
->SearchForward( aCellStr, &nStart, &nEnd ); // from 614 on, nEnd is behind the found text if (bMatch && bMatchWholeCell
&& (nStart != 0 || nEnd != aCellStr.getLength()))
bMatch = false; // RegExp must match entire cell string
bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
} else
{ if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
{ if (bMatchWholeCell)
{ // TODO: Use shared string for fast equality check.
OUString aStr = rEntry.GetQueryItem().maString.getString();
bOk = rTransliteration.isEqual(aCellStr, aStr); bool bHasStar = false;
sal_Int32 nIndex; if (( nIndex = aStr.indexOf('*') ) != -1)
bHasStar = true; if (bHasStar && (nIndex>0))
{ for (sal_Int32 j=0;(j<nIndex) && (j< aCellStr.getLength()) ; j++)
{ if (aCellStr[j] == aStr[j])
{
bOk=true;
} else
{
bOk=false; break;
}
}
}
} else
{
OUString aQueryStr = rEntry.GetQueryItem().maString.getString();
css::uno::Sequence< sal_Int32 > xOff; const LanguageType nLang = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
OUString aCell = rTransliteration.transliterate(
aCellStr, nLang, 0, aCellStr.getLength(), &xOff);
OUString aQuer = rTransliteration.transliterate(
aQueryStr, nLang, 0, aQueryStr.getLength(), &xOff);
bOk = (aCell.indexOf( aQuer ) != -1);
} if (rEntry.eOp == SC_NOT_EQUAL)
bOk = !bOk;
} else
{ // use collator here because data was probably sorted
sal_Int32 nCompare = rCollator.compareString(
aCellStr, rEntry.GetQueryItem().maString.getString()); switch (rEntry.eOp)
{ case SC_LESS :
bOk = (nCompare < 0); break; case SC_GREATER :
bOk = (nCompare > 0); break; case SC_LESS_EQUAL :
bOk = (nCompare <= 0); break; case SC_GREATER_EQUAL :
bOk = (nCompare >= 0); break; case SC_NOT_EQUAL:
OSL_FAIL("SC_NOT_EQUAL"); break; case SC_TOPVAL: case SC_BOTVAL: case SC_TOPPERC: case SC_BOTPERC: default: break;
}
}
}
}
const Field& rField = *maFields[nDim]; if (o3tl::make_unsigned(nRow) >= rField.maData.size())
{ // nRow is in the trailing empty rows area. if (bRepeatIfEmpty)
nRow = rField.maData.size()-1; // Move to the last non-empty row. else // Return the last item, which should always be empty if the // initialization has skipped trailing empty rows. return rField.maItems.size()-1;
// TODO: Find a way to determine the dominant number format in presence of // multiple number formats in the same field. return maFields[nDim]->mnNumFormat;
}
if (nDim < mnColumnCount)
{ // source field. const ScDPItemDataVec& rItems = maFields[nDim]->maItems; for (size_t i = 0, n = rItems.size(); i < n; ++i)
{ if (rItems[i] == rItem) return i;
}
if (!maFields[nDim]->mpGroup) return -1;
// grouped source field. const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems; for (size_t i = 0, n = rGI.size(); i < n; ++i)
{ if (rGI[i] == rItem) return rItems.size() + i;
} return -1;
}
// group field.
nDim -= mnColumnCount; if (o3tl::make_unsigned(nDim) < maGroupFields.size())
{ const ScDPItemDataVec& rGI = maGroupFields[nDim]->maItems; for (size_t i = 0, n = rGI.size(); i < n; ++i)
{ if (rGI[i] == rItem) return i;
}
}
return -1;
}
// static
sal_uInt32 ScDPCache::GetLocaleIndependentFormat(const ScInterpreterContext& rContext, sal_uInt32 nNumFormat)
{ // For a date or date+time format use ISO format so it works across locales // and can be matched against string based item queries. For time use 24h // format. All others use General format, no currency, percent, ... // Use en-US locale for all. switch (rContext.NFGetType(nNumFormat))
{ case SvNumFormatType::DATE: return rContext.NFGetFormatIndex( NF_DATE_ISO_YYYYMMDD, LANGUAGE_ENGLISH_US); case SvNumFormatType::TIME: return rContext.NFGetFormatIndex( NF_TIME_HHMMSS, LANGUAGE_ENGLISH_US); case SvNumFormatType::DATETIME: return rContext.NFGetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, LANGUAGE_ENGLISH_US); default: return rContext.NFGetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_ENGLISH_US);
}
}
ScDPItemData::Type eType = rItem.GetType(); if (eType == ScDPItemData::Value)
{ // Format value using the stored number format.
ScInterpreterContext& rContext = mrDoc.GetNonThreadedContext();
sal_uInt32 nNumFormat = GetNumberFormat(nDim); if (bLocaleIndependent) return GetLocaleIndependentFormattedString( rItem.GetValue(), rContext, nNumFormat);
switch (nType)
{ case sheet::DataPilotFieldGroupBy::YEARS: return pNames[1]; case sheet::DataPilotFieldGroupBy::QUARTERS: return pNames[2]; case sheet::DataPilotFieldGroupBy::MONTHS: return pNames[3]; case sheet::DataPilotFieldGroupBy::DAYS: return pNames[4]; case sheet::DataPilotFieldGroupBy::HOURS: return pNames[5]; case sheet::DataPilotFieldGroupBy::MINUTES: return pNames[6]; case sheet::DataPilotFieldGroupBy::SECONDS: return pNames[7]; default:
;
}
return pNames[0];
}
}
void ScDPCache::Dump() const
{ // Change these flags to fit your debugging needs. bool bDumpItems = false; bool bDumpSourceData = false;
cout << "--- pivot cache dump" << endl;
{
size_t i = 0; for (constauto& rxField : maFields)
{ const Field& fld = *rxField;
cout << "* source dimension: " << GetDimensionName(i) << " (ID = " << i << ")" << endl;
cout << " item count: " << fld.maItems.size() << endl; if (bDumpItems)
dumpItems(*this, i, fld.maItems, 0); if (fld.mpGroup)
{
cout << " group item count: " << fld.mpGroup->maItems.size() << endl;
cout << " group type: " << getGroupTypeName(fld.mpGroup->mnGroupType) << endl; if (bDumpItems)
dumpItems(*this, i, fld.mpGroup->maItems, fld.maItems.size());
}
if (bDumpSourceData)
{
cout << " source data (re-constructed):" << endl;
dumpSourceData(*this, i, fld.maItems, fld.maData);
}
++i;
}
}
{
size_t i = maFields.size(); for (constauto& rxGroupField : maGroupFields)
{ const GroupItems& gi = *rxGroupField;
cout << "* group dimension: (unnamed) (ID = " << i << ")" << endl;
cout << " item count: " << gi.maItems.size() << endl;
cout << " group type: " << getGroupTypeName(gi.mnGroupType) << endl; if (bDumpItems)
dumpItems(*this, i, gi.maItems, 0);
++i;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.