xref: /core/sc/source/core/data/dpcache.cxx (revision b3897439)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <dpcache.hxx>
21 
22 #include <document.hxx>
23 #include <queryentry.hxx>
24 #include <queryparam.hxx>
25 #include <dpobject.hxx>
26 #include <globstr.hrc>
27 #include <scresid.hxx>
28 #include <docoptio.hxx>
29 #include <dpitemdata.hxx>
30 #include <dputil.hxx>
31 #include <dpnumgroupinfo.hxx>
32 #include <columniterator.hxx>
33 #include <cellvalue.hxx>
34 
35 #include <comphelper/parallelsort.hxx>
36 #include <rtl/math.hxx>
37 #include <unotools/charclass.hxx>
38 #include <unotools/textsearch.hxx>
39 #include <unotools/localedatawrapper.hxx>
40 #include <unotools/collatorwrapper.hxx>
41 #include <svl/numformat.hxx>
42 #include <svl/zforlist.hxx>
43 #include <o3tl/safeint.hxx>
44 #include <osl/diagnose.h>
45 
46 #if DUMP_PIVOT_TABLE
47 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
48 #endif
49 
50 // TODO : Threaded pivot cache operation is disabled until we can figure out
51 // ways to make the edit engine and number formatter codes thread-safe in a
52 // proper fashion.
53 #define ENABLE_THREADED_PIVOT_CACHE 0
54 
55 #if ENABLE_THREADED_PIVOT_CACHE
56 #include <thread>
57 #include <future>
58 #include <queue>
59 #endif
60 
61 using namespace ::com::sun::star;
62 
63 using ::com::sun::star::uno::Exception;
64 
GroupItems()65 ScDPCache::GroupItems::GroupItems() : mnGroupType(0) {}
66 
GroupItems(const ScDPNumGroupInfo & rInfo,sal_Int32 nGroupType)67 ScDPCache::GroupItems::GroupItems(const ScDPNumGroupInfo& rInfo, sal_Int32 nGroupType) :
68     maInfo(rInfo), mnGroupType(nGroupType) {}
69 
Field()70 ScDPCache::Field::Field() : mnNumFormat(0) {}
71 
ScDPCache(ScDocument & rDoc)72 ScDPCache::ScDPCache(ScDocument& rDoc) :
73     mrDoc( rDoc ),
74     mnColumnCount ( 0 ),
75     maEmptyRows(0, rDoc.GetMaxRowCount(), true),
76     mnDataSize(-1),
77     mnRowCount(0),
78     mbDisposing(false)
79 {
80 }
81 
82 namespace {
83 
84 struct ClearObjectSource
85 {
operator ()__anon5fd34f840111::ClearObjectSource86     void operator() (ScDPObject* p) const
87     {
88         p->ClearTableData();
89     }
90 };
91 
92 }
93 
~ScDPCache()94 ScDPCache::~ScDPCache()
95 {
96     // Make sure no live ScDPObject instances hold reference to this cache any
97     // more.
98     mbDisposing = true;
99     std::for_each(maRefObjects.begin(), maRefObjects.end(), ClearObjectSource());
100 }
101 
102 namespace {
103 
104 /**
105  * While the macro interpret level is incremented, the formula cells are
106  * (semi-)guaranteed to be interpreted.
107  */
108 class MacroInterpretIncrementer
109 {
110 public:
MacroInterpretIncrementer(ScDocument & rDoc)111     explicit MacroInterpretIncrementer(ScDocument& rDoc) :
112         mrDoc(rDoc)
113     {
114         mrDoc.IncMacroInterpretLevel();
115     }
~MacroInterpretIncrementer()116     ~MacroInterpretIncrementer()
117     {
118         mrDoc.DecMacroInterpretLevel();
119     }
120 private:
121     ScDocument& mrDoc;
122 };
123 
internString(ScDPCache::StringSetType & rPool,const OUString & rStr)124 rtl_uString* internString( ScDPCache::StringSetType& rPool, const OUString& rStr )
125 {
126     return rPool.insert(rStr).first->pData;
127 }
128 
createLabelString(const ScDocument & rDoc,SCCOL nCol,const ScRefCellValue & rCell)129 OUString createLabelString( const ScDocument& rDoc, SCCOL nCol, const ScRefCellValue& rCell )
130 {
131     OUString aDocStr = rCell.getRawString(rDoc);
132 
133     if (aDocStr.isEmpty())
134     {
135         // Replace an empty label string with column name.
136 
137         ScAddress aColAddr(nCol, 0, 0);
138         aDocStr = ScResId(STR_COLUMN) + " " + aColAddr.Format(ScRefFlags::COL_VALID);
139     }
140     return aDocStr;
141 }
142 
initFromCell(ScDPCache::StringSetType & rStrPool,const ScDocument & rDoc,const ScAddress & rPos,const ScRefCellValue & rCell,ScDPItemData & rData,sal_uInt32 & rNumFormat)143 void initFromCell(
144     ScDPCache::StringSetType& rStrPool, const ScDocument& rDoc, const ScAddress& rPos,
145     const ScRefCellValue& rCell, ScDPItemData& rData, sal_uInt32& rNumFormat)
146 {
147     OUString aDocStr = rCell.getRawString(rDoc);
148     rNumFormat = 0;
149 
150     if (rCell.hasError())
151     {
152         rData.SetErrorStringInterned(internString(rStrPool, rDoc.GetString(rPos.Col(), rPos.Row(), rPos.Tab())));
153     }
154     else if (rCell.hasNumeric())
155     {
156         double fVal = rCell.getRawValue();
157         rNumFormat = rDoc.GetNumberFormat(rPos);
158         rData.SetValue(fVal);
159     }
160     else if (!rCell.isEmpty())
161     {
162         rData.SetStringInterned(internString(rStrPool, aDocStr));
163     }
164     else
165         rData.SetEmpty();
166 }
167 
168 struct Bucket
169 {
170     ScDPItemData maValue;
171     SCROW mnOrderIndex;
172     SCROW mnDataIndex;
Bucket__anon5fd34f840211::Bucket173     Bucket() :
174         mnOrderIndex(0), mnDataIndex(0) {}
Bucket__anon5fd34f840211::Bucket175     Bucket(const ScDPItemData& rValue, SCROW nData) :
176         maValue(rValue), mnOrderIndex(0), mnDataIndex(nData) {}
177 };
178 
179 #if DEBUG_PIVOT_TABLE
180 #include <iostream>
181 using std::cout;
182 using std::endl;
183 
184 struct PrintBucket
185 {
operator ()__anon5fd34f840211::PrintBucket186     void operator() (const Bucket& v) const
187     {
188         cout << "value: " << v.maValue.GetValue() << "  order index: " << v.mnOrderIndex << "  data index: " << v.mnDataIndex << endl;
189     }
190 };
191 
192 #endif
193 
194 struct LessByValue
195 {
operator ()__anon5fd34f840211::LessByValue196     bool operator() (const Bucket& left, const Bucket& right) const
197     {
198         return left.maValue < right.maValue;
199     }
200 };
201 
202 struct LessByOrderIndex
203 {
operator ()__anon5fd34f840211::LessByOrderIndex204     bool operator() (const Bucket& left, const Bucket& right) const
205     {
206         return left.mnOrderIndex < right.mnOrderIndex;
207     }
208 };
209 
210 struct LessByDataIndex
211 {
operator ()__anon5fd34f840211::LessByDataIndex212     bool operator() (const Bucket& left, const Bucket& right) const
213     {
214         return left.mnDataIndex < right.mnDataIndex;
215     }
216 };
217 
218 struct EqualByOrderIndex
219 {
operator ()__anon5fd34f840211::EqualByOrderIndex220     bool operator() (const Bucket& left, const Bucket& right) const
221     {
222         return left.mnOrderIndex == right.mnOrderIndex;
223     }
224 };
225 
226 class PushBackValue
227 {
228     ScDPCache::ScDPItemDataVec& mrItems;
229 public:
PushBackValue(ScDPCache::ScDPItemDataVec & _items)230     explicit PushBackValue(ScDPCache::ScDPItemDataVec& _items) : mrItems(_items) {}
operator ()(const Bucket & v)231     void operator() (const Bucket& v)
232     {
233         mrItems.push_back(v.maValue);
234     }
235 };
236 
237 class PushBackOrderIndex
238 {
239     ScDPCache::IndexArrayType& mrData;
240 public:
PushBackOrderIndex(ScDPCache::IndexArrayType & _items)241     explicit PushBackOrderIndex(ScDPCache::IndexArrayType& _items) : mrData(_items) {}
operator ()(const Bucket & v)242     void operator() (const Bucket& v)
243     {
244         mrData.push_back(v.mnOrderIndex);
245     }
246 };
247 
processBuckets(std::vector<Bucket> & aBuckets,ScDPCache::Field & rField)248 void processBuckets(std::vector<Bucket>& aBuckets, ScDPCache::Field& rField)
249 {
250     if (aBuckets.empty())
251         return;
252 
253     // Sort by the value.
254     comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByValue());
255 
256     {
257         // Set order index such that unique values have identical index value.
258         SCROW nCurIndex = 0;
259         std::vector<Bucket>::iterator it = aBuckets.begin(), itEnd = aBuckets.end();
260         ScDPItemData aPrev = it->maValue;
261         it->mnOrderIndex = nCurIndex;
262         for (++it; it != itEnd; ++it)
263         {
264             if (!aPrev.IsCaseInsEqual(it->maValue))
265                 ++nCurIndex;
266 
267             it->mnOrderIndex = nCurIndex;
268             aPrev = it->maValue;
269         }
270     }
271 
272     // Re-sort the bucket this time by the data index.
273     comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByDataIndex());
274 
275     // Copy the order index series into the field object.
276     rField.maData.reserve(aBuckets.size());
277     std::for_each(aBuckets.begin(), aBuckets.end(), PushBackOrderIndex(rField.maData));
278 
279     // Sort by the value again.
280     comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByOrderIndex());
281 
282     // Unique by value.
283     std::vector<Bucket>::iterator itUniqueEnd =
284         std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex());
285 
286     // Copy the unique values into items.
287     std::vector<Bucket>::iterator itBeg = aBuckets.begin();
288     size_t nLen = distance(itBeg, itUniqueEnd);
289     rField.maItems.reserve(nLen);
290     std::for_each(itBeg, itUniqueEnd, PushBackValue(rField.maItems));
291 }
292 
293 struct InitColumnData
294 {
295     ScDPCache::EmptyRowsType maEmptyRows;
296     OUString maLabel;
297 
298     ScDPCache::StringSetType* mpStrPool;
299     ScDPCache::Field* mpField;
300 
301     SCCOL mnCol;
302 
InitColumnData__anon5fd34f840211::InitColumnData303     InitColumnData(ScSheetLimits const & rSheetLimits) :
304         maEmptyRows(0, rSheetLimits.GetMaxRowCount(), true),
305         mpStrPool(nullptr),
306         mpField(nullptr),
307         mnCol(-1) {}
308 
init__anon5fd34f840211::InitColumnData309     void init( SCCOL nCol, ScDPCache::StringSetType* pStrPool, ScDPCache::Field* pField )
310     {
311         mpStrPool = pStrPool;
312         mpField = pField;
313         mnCol = nCol;
314     }
315 };
316 
317 struct InitDocData
318 {
319     ScDocument& mrDoc;
320     SCTAB mnDocTab;
321     SCROW mnStartRow;
322     SCROW mnEndRow;
323     bool mbTailEmptyRows;
324 
InitDocData__anon5fd34f840211::InitDocData325     InitDocData(ScDocument& rDoc) :
326         mrDoc(rDoc),
327         mnDocTab(-1),
328         mnStartRow(-1),
329         mnEndRow(-1),
330         mbTailEmptyRows(false) {}
331 };
332 
333 typedef std::unordered_set<OUString> LabelSet;
334 
normalizeAddLabel(const OUString & rLabel,std::vector<OUString> & rLabels,LabelSet & rExistingNames)335 void normalizeAddLabel(const OUString& rLabel, std::vector<OUString>& rLabels, LabelSet& rExistingNames)
336 {
337     const OUString aLabelLower = ScGlobal::getCharClass().lowercase(rLabel);
338     sal_Int32 nSuffix = 1;
339     OUString aNewLabel = rLabel;
340     OUString aNewLabelLower = aLabelLower;
341     while (true)
342     {
343         if (!rExistingNames.count(aNewLabelLower))
344         {
345             // this is a unique label.
346             rLabels.push_back(aNewLabel);
347             rExistingNames.insert(aNewLabelLower);
348             break;
349         }
350 
351         // This name already exists.
352         aNewLabel = rLabel + OUString::number(++nSuffix);
353         aNewLabelLower = aLabelLower + OUString::number(nSuffix);
354     }
355 }
356 
normalizeLabels(const std::vector<InitColumnData> & rColData)357 std::vector<OUString> normalizeLabels(const std::vector<InitColumnData>& rColData)
358 {
359     std::vector<OUString> aLabels;
360     aLabels.reserve(rColData.size() + 1);
361 
362     LabelSet aExistingNames;
363     normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
364 
365     for (const InitColumnData& rCol : rColData)
366         normalizeAddLabel(rCol.maLabel, aLabels, aExistingNames);
367 
368     return aLabels;
369 }
370 
normalizeLabels(const ScDPCache::DBConnector & rDB,const sal_Int32 nLabelCount)371 std::vector<OUString> normalizeLabels(const ScDPCache::DBConnector& rDB, const sal_Int32 nLabelCount)
372 {
373     std::vector<OUString> aLabels;
374     aLabels.reserve(nLabelCount + 1);
375 
376     LabelSet aExistingNames;
377     normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
378 
379     for (sal_Int32 nCol = 0; nCol < nLabelCount; ++nCol)
380     {
381         OUString aColTitle = rDB.getColumnLabel(nCol);
382         normalizeAddLabel(aColTitle, aLabels, aExistingNames);
383     }
384 
385     return aLabels;
386 }
387 
initColumnFromDoc(InitDocData & rDocData,InitColumnData & rColData)388 void initColumnFromDoc( InitDocData& rDocData, InitColumnData &rColData )
389 {
390     ScDPCache::Field& rField = *rColData.mpField;
391     ScDocument& rDoc = rDocData.mrDoc;
392     SCTAB nDocTab = rDocData.mnDocTab;
393     SCCOL nCol = rColData.mnCol;
394     SCROW nStartRow = rDocData.mnStartRow;
395     SCROW nEndRow = rDocData.mnEndRow;
396     bool bTailEmptyRows = rDocData.mbTailEmptyRows;
397 
398     std::optional<sc::ColumnIterator> pIter =
399         rDoc.GetColumnIterator(nDocTab, nCol, nStartRow, nEndRow);
400     assert(pIter);
401     assert(pIter->hasCell());
402 
403     ScDPItemData aData;
404 
405     rColData.maLabel = createLabelString(rDoc, nCol, pIter->getCell());
406     pIter->next();
407 
408     std::vector<Bucket> aBuckets;
409     aBuckets.reserve(nEndRow-nStartRow); // skip the topmost label cell.
410 
411     // Push back all original values.
412     for (SCROW i = 0, n = nEndRow-nStartRow; i < n; ++i, pIter->next())
413     {
414         assert(pIter->hasCell());
415 
416         sal_uInt32 nNumFormat = 0;
417         ScAddress aPos(nCol, pIter->getRow(), nDocTab);
418         initFromCell(*rColData.mpStrPool, rDoc, aPos, pIter->getCell(), aData, nNumFormat);
419 
420         aBuckets.emplace_back(aData, i);
421 
422         if (!aData.IsEmpty())
423         {
424             rColData.maEmptyRows.insert_back(i, i+1, false);
425             if (nNumFormat)
426                 // Only take non-default number format.
427                 rField.mnNumFormat = nNumFormat;
428         }
429     }
430 
431     processBuckets(aBuckets, rField);
432 
433     if (bTailEmptyRows)
434     {
435         // If the last item is not empty, append one. Note that the items
436         // are sorted, and empty item should come last when sorted.
437         if (rField.maItems.empty() || !rField.maItems.back().IsEmpty())
438         {
439             aData.SetEmpty();
440             rField.maItems.push_back(aData);
441         }
442     }
443 }
444 
445 #if ENABLE_THREADED_PIVOT_CACHE
446 
447 class ThreadQueue
448 {
449     using FutureType = std::future<void>;
450     std::queue<FutureType> maQueue;
451     std::mutex maMutex;
452     std::condition_variable maCond;
453 
454     size_t mnMaxQueue;
455 
456 public:
ThreadQueue(size_t nMaxQueue)457     ThreadQueue( size_t nMaxQueue ) : mnMaxQueue(nMaxQueue) {}
458 
push(std::function<void ()> aFunc)459     void push( std::function<void()> aFunc )
460     {
461         std::unique_lock<std::mutex> lock(maMutex);
462 
463         while (maQueue.size() >= mnMaxQueue)
464             maCond.wait(lock);
465 
466         FutureType f = std::async(std::launch::async, aFunc);
467         maQueue.push(std::move(f));
468         lock.unlock();
469 
470         maCond.notify_one();
471     }
472 
waitForOne()473     void waitForOne()
474     {
475         std::unique_lock<std::mutex> lock(maMutex);
476 
477         while (maQueue.empty())
478             maCond.wait(lock);
479 
480         FutureType ret = std::move(maQueue.front());
481         maQueue.pop();
482         lock.unlock();
483 
484         ret.get(); // This may throw if an exception was thrown on the async thread.
485 
486         maCond.notify_one();
487     }
488 };
489 
490 class ThreadScopedGuard
491 {
492     std::thread maThread;
493 public:
ThreadScopedGuard(std::thread thread)494     ThreadScopedGuard(std::thread thread) : maThread(std::move(thread)) {}
ThreadScopedGuard(ThreadScopedGuard && other)495     ThreadScopedGuard(ThreadScopedGuard&& other) : maThread(std::move(other.maThread)) {}
496 
497     ThreadScopedGuard(const ThreadScopedGuard&) = delete;
498     ThreadScopedGuard& operator= (const ThreadScopedGuard&) = delete;
499 
~ThreadScopedGuard()500     ~ThreadScopedGuard()
501     {
502         maThread.join();
503     }
504 };
505 
506 #endif
507 
508 }
509 
InitFromDoc(ScDocument & rDoc,const ScRange & rRange)510 void ScDPCache::InitFromDoc(ScDocument& rDoc, const ScRange& rRange)
511 {
512     Clear();
513 
514     InitDocData aDocData(rDoc);
515 
516     // Make sure the formula cells within the data range are interpreted
517     // during this call, for this method may be called from the interpretation
518     // of GETPIVOTDATA, which disables nested formula interpretation without
519     // increasing the macro level.
520     MacroInterpretIncrementer aMacroInc(rDoc);
521 
522     aDocData.mnStartRow = rRange.aStart.Row();  // start of data
523     aDocData.mnEndRow = rRange.aEnd.Row();
524 
525     // Sanity check
526     if (!GetDoc().ValidRow(aDocData.mnStartRow) || !GetDoc().ValidRow(aDocData.mnEndRow) || aDocData.mnEndRow <= aDocData.mnStartRow)
527         return;
528 
529     SCCOL nStartCol = rRange.aStart.Col();
530     SCCOL nEndCol = rRange.aEnd.Col();
531     aDocData.mnDocTab = rRange.aStart.Tab();
532 
533     mnColumnCount = nEndCol - nStartCol + 1;
534 
535     // this row count must include the trailing empty rows.
536     mnRowCount = aDocData.mnEndRow - aDocData.mnStartRow; // skip the topmost label row.
537 
538     // Skip trailing empty rows if exists.
539     SCCOL nCol1 = nStartCol, nCol2 = nEndCol;
540     SCROW nRow1 = aDocData.mnStartRow, nRow2 = aDocData.mnEndRow;
541     rDoc.ShrinkToDataArea(aDocData.mnDocTab, nCol1, nRow1, nCol2, nRow2);
542     aDocData.mbTailEmptyRows = aDocData.mnEndRow > nRow2; // Trailing empty rows exist.
543     aDocData.mnEndRow = nRow2;
544 
545     if (aDocData.mnEndRow <= aDocData.mnStartRow)
546     {
547         // Check this again since the end row position has changed. It's
548         // possible that the new end row becomes lower than the start row
549         // after the shrinkage.
550         Clear();
551         return;
552     }
553 
554     maStringPools.resize(mnColumnCount);
555     std::vector<InitColumnData> aColData(mnColumnCount, InitColumnData(rDoc.GetSheetLimits()));
556     maFields.reserve(mnColumnCount);
557     for (SCCOL i = 0; i < mnColumnCount; ++i)
558         maFields.push_back(std::make_unique<Field>());
559 
560     maLabelNames.reserve(mnColumnCount+1);
561 
562     // Ensure that none of the formula cells in the data range are dirty.
563     rDoc.EnsureFormulaCellResults(rRange);
564 
565 #if ENABLE_THREADED_PIVOT_CACHE
566     ThreadQueue aQueue(std::thread::hardware_concurrency());
567 
568     auto aFuncLaunchFieldThreads = [&]()
569     {
570         for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
571         {
572             size_t nDim = nCol - nStartCol;
573             InitColumnData& rColData = aColData[nDim];
574             rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
575 
576             auto func = [&aDocData,&rColData]()
577             {
578                 initColumnFromDoc(aDocData, rColData);
579             };
580 
581             aQueue.push(std::move(func));
582         }
583     };
584 
585     {
586         // Launch a separate thread that in turn spawns async threads to populate the fields.
587         std::thread t(aFuncLaunchFieldThreads);
588         ThreadScopedGuard sg(std::move(t));
589 
590         // Wait for all the async threads to complete on the main thread.
591         for (SCCOL i = 0; i < mnColumnCount; ++i)
592             aQueue.waitForOne();
593     }
594 
595 #else
596     for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
597     {
598         size_t nDim = nCol - nStartCol;
599         InitColumnData& rColData = aColData[nDim];
600         rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
601 
602         initColumnFromDoc(aDocData, rColData);
603     }
604 #endif
605 
606     maLabelNames = normalizeLabels(aColData);
607 
608     // Merge all non-empty rows data.
609     for (const InitColumnData& rCol : aColData)
610     {
611         EmptyRowsType::const_segment_iterator it = rCol.maEmptyRows.begin_segment();
612         EmptyRowsType::const_segment_iterator ite = rCol.maEmptyRows.end_segment();
613         EmptyRowsType::const_iterator pos = maEmptyRows.begin();
614 
615         for (; it != ite; ++it)
616         {
617             if (!it->value)
618                 // Non-empty segment found.  Record it.
619                 pos = maEmptyRows.insert(pos, it->start, it->end, false).first;
620         }
621     }
622 
623     PostInit();
624 }
625 
InitFromDataBase(DBConnector & rDB)626 bool ScDPCache::InitFromDataBase(DBConnector& rDB)
627 {
628     Clear();
629 
630     try
631     {
632         mnColumnCount = rDB.getColumnCount();
633         maStringPools.resize(mnColumnCount);
634         maFields.clear();
635         maFields.reserve(mnColumnCount);
636         for (SCCOL i = 0; i < mnColumnCount; ++i)
637             maFields.push_back(std::make_unique<Field>());
638 
639         // Get column titles and types.
640         maLabelNames = normalizeLabels(rDB, mnColumnCount);
641 
642         std::vector<Bucket> aBuckets;
643         ScDPItemData aData;
644         for (sal_Int32 nCol = 0; nCol < mnColumnCount; ++nCol)
645         {
646             if (!rDB.first())
647                 continue;
648 
649             aBuckets.clear();
650             Field& rField = *maFields[nCol];
651             SCROW nRow = 0;
652             do
653             {
654                 SvNumFormatType nFormatType = SvNumFormatType::UNDEFINED;
655                 aData.SetEmpty();
656                 rDB.getValue(nCol, aData, nFormatType);
657                 aBuckets.emplace_back(aData, nRow);
658                 if (!aData.IsEmpty())
659                 {
660                     maEmptyRows.insert_back(nRow, nRow+1, false);
661                     ScInterpreterContext& rContext = mrDoc.GetNonThreadedContext();
662                     rField.mnNumFormat = rContext.NFGetStandardFormat(nFormatType);
663                 }
664 
665                 ++nRow;
666             }
667             while (rDB.next());
668 
669             processBuckets(aBuckets, rField);
670         }
671 
672         rDB.finish();
673 
674         if (!maFields.empty())
675             mnRowCount = maFields[0]->maData.size();
676 
677         PostInit();
678         return true;
679     }
680     catch (const Exception&)
681     {
682         return false;
683     }
684 }
685 
ValidQuery(SCROW nRow,const ScQueryParam & rParam) const686 bool ScDPCache::ValidQuery( SCROW nRow, const ScQueryParam &rParam) const
687 {
688     if (!rParam.GetEntryCount())
689         return true;
690 
691     if (!rParam.GetEntry(0).bDoQuery)
692         return true;
693 
694     bool bMatchWholeCell = mrDoc.GetDocOptions().IsMatchWholeCell();
695 
696     SCSIZE nEntryCount = rParam.GetEntryCount();
697     std::vector<bool> aPassed(nEntryCount, false);
698 
699     tools::Long nPos = -1;
700     CollatorWrapper& rCollator = ScGlobal::GetCollator(rParam.bCaseSens);
701     ::utl::TransliterationWrapper& rTransliteration = ScGlobal::GetTransliteration(rParam.bCaseSens);
702 
703     for (size_t i = 0; i < nEntryCount && rParam.GetEntry(i).bDoQuery; ++i)
704     {
705         const ScQueryEntry& rEntry = rParam.GetEntry(i);
706         const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
707         // we can only handle one single direct query
708         // #i115431# nField in QueryParam is the sheet column, not the field within the source range
709         SCCOL nQueryCol = static_cast<SCCOL>(rEntry.nField);
710         if ( nQueryCol < rParam.nCol1 )
711             nQueryCol = rParam.nCol1;
712         if ( nQueryCol > rParam.nCol2 )
713             nQueryCol = rParam.nCol2;
714         SCCOL nSourceField = nQueryCol - rParam.nCol1;
715         SCROW nId = GetItemDataId( nSourceField, nRow, false );
716         const ScDPItemData* pCellData = GetItemDataById( nSourceField, nId );
717 
718         bool bOk = false;
719 
720         if (rEntry.GetQueryItem().meType == ScQueryEntry::ByEmpty)
721         {
722             if (rEntry.IsQueryByEmpty())
723                 bOk = pCellData->IsEmpty();
724             else
725             {
726                 assert(rEntry.IsQueryByNonEmpty());
727                 bOk = !pCellData->IsEmpty();
728             }
729         }
730         else if (rEntry.GetQueryItem().meType != ScQueryEntry::ByString && pCellData->IsValue())
731         {   // by Value
732             double nCellVal = pCellData->GetValue();
733 
734             switch (rEntry.eOp)
735             {
736                 case SC_EQUAL :
737                     bOk = ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
738                     break;
739                 case SC_LESS :
740                     bOk = (nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
741                     break;
742                 case SC_GREATER :
743                     bOk = (nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
744                     break;
745                 case SC_LESS_EQUAL :
746                     bOk = (nCellVal < rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
747                     break;
748                 case SC_GREATER_EQUAL :
749                     bOk = (nCellVal > rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
750                     break;
751                 case SC_NOT_EQUAL :
752                     bOk = !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
753                     break;
754                 default:
755                     bOk= false;
756                     break;
757             }
758         }
759         else if ((rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
760                  || (rEntry.GetQueryItem().meType == ScQueryEntry::ByString
761                      && pCellData->HasStringData() )
762                 )
763         {   // by String
764             OUString  aCellStr = pCellData->GetString();
765 
766             bool bRealWildOrRegExp = (rParam.eSearchType != utl::SearchParam::SearchType::Normal &&
767                     ((rEntry.eOp == SC_EQUAL) || (rEntry.eOp == SC_NOT_EQUAL)));
768             if (bRealWildOrRegExp)
769             {
770                 sal_Int32 nStart = 0;
771                 sal_Int32 nEnd   = aCellStr.getLength();
772 
773                 bool bMatch = rEntry.GetSearchTextPtr( rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell )
774                                 ->SearchForward( aCellStr, &nStart, &nEnd );
775                 // from 614 on, nEnd is behind the found text
776                 if (bMatch && bMatchWholeCell
777                     && (nStart != 0 || nEnd != aCellStr.getLength()))
778                     bMatch = false;    // RegExp must match entire cell string
779 
780                 bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
781             }
782             else
783             {
784                 if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
785                 {
786                     if (bMatchWholeCell)
787                     {
788                         // TODO: Use shared string for fast equality check.
789                         OUString aStr = rEntry.GetQueryItem().maString.getString();
790                         bOk = rTransliteration.isEqual(aCellStr, aStr);
791                         bool bHasStar = false;
792                         sal_Int32 nIndex;
793                         if (( nIndex = aStr.indexOf('*') ) != -1)
794                             bHasStar = true;
795                         if (bHasStar && (nIndex>0))
796                         {
797                             for (sal_Int32 j=0;(j<nIndex) && (j< aCellStr.getLength()) ; j++)
798                             {
799                                 if (aCellStr[j] == aStr[j])
800                                 {
801                                     bOk=true;
802                                 }
803                                 else
804                                 {
805                                     bOk=false;
806                                     break;
807                                 }
808                             }
809                         }
810                     }
811                     else
812                     {
813                         OUString aQueryStr = rEntry.GetQueryItem().maString.getString();
814                         css::uno::Sequence< sal_Int32 > xOff;
815                         const LanguageType nLang = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
816                         OUString aCell = rTransliteration.transliterate(
817                             aCellStr, nLang, 0, aCellStr.getLength(), &xOff);
818                         OUString aQuer = rTransliteration.transliterate(
819                             aQueryStr, nLang, 0, aQueryStr.getLength(), &xOff);
820                         bOk = (aCell.indexOf( aQuer ) != -1);
821                     }
822                     if (rEntry.eOp == SC_NOT_EQUAL)
823                         bOk = !bOk;
824                 }
825                 else
826                 {   // use collator here because data was probably sorted
827                     sal_Int32 nCompare = rCollator.compareString(
828                         aCellStr, rEntry.GetQueryItem().maString.getString());
829                     switch (rEntry.eOp)
830                     {
831                         case SC_LESS :
832                             bOk = (nCompare < 0);
833                             break;
834                         case SC_GREATER :
835                             bOk = (nCompare > 0);
836                             break;
837                         case SC_LESS_EQUAL :
838                             bOk = (nCompare <= 0);
839                             break;
840                         case SC_GREATER_EQUAL :
841                             bOk = (nCompare >= 0);
842                             break;
843                         case SC_NOT_EQUAL:
844                             OSL_FAIL("SC_NOT_EQUAL");
845                             break;
846                         case SC_TOPVAL:
847                         case SC_BOTVAL:
848                         case SC_TOPPERC:
849                         case SC_BOTPERC:
850                         default:
851                             break;
852                     }
853                 }
854             }
855         }
856 
857         if (nPos == -1)
858         {
859             nPos++;
860             aPassed[nPos] = bOk;
861         }
862         else
863         {
864             if (rEntry.eConnect == SC_AND)
865             {
866                 aPassed[nPos] = aPassed[nPos] && bOk;
867             }
868             else
869             {
870                 nPos++;
871                 aPassed[nPos] = bOk;
872             }
873         }
874     }
875 
876     for (tools::Long j=1; j <= nPos; j++)
877         aPassed[0] = aPassed[0] || aPassed[j];
878 
879     bool bRet = aPassed[0];
880     return bRet;
881 }
882 
GetDoc() const883 ScDocument& ScDPCache::GetDoc() const
884 {
885     return mrDoc;
886 }
887 
GetColumnCount() const888 tools::Long ScDPCache::GetColumnCount() const
889 {
890     return mnColumnCount;
891 }
892 
IsRowEmpty(SCROW nRow) const893 bool ScDPCache::IsRowEmpty(SCROW nRow) const
894 {
895     bool bEmpty = true;
896     maEmptyRows.search_tree(nRow, bEmpty);
897     return bEmpty;
898 }
899 
GetGroupItems(tools::Long nDim) const900 const ScDPCache::GroupItems* ScDPCache::GetGroupItems(tools::Long nDim) const
901 {
902     if (nDim < 0)
903         return nullptr;
904 
905     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
906     if (nDim < nSourceCount)
907         return maFields[nDim]->mpGroup.get();
908 
909     nDim -= nSourceCount;
910     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
911         return maGroupFields[nDim].get();
912 
913     return nullptr;
914 }
915 
GetDimensionName(std::vector<OUString>::size_type nDim) const916 OUString ScDPCache::GetDimensionName(std::vector<OUString>::size_type nDim) const
917 {
918     OSL_ENSURE(nDim < maLabelNames.size()-1 , "ScDPTableDataCache::GetDimensionName");
919     OSL_ENSURE(maLabelNames.size() == static_cast <sal_uInt16> (mnColumnCount+1), "ScDPTableDataCache::GetDimensionName");
920 
921     if ( nDim+1 < maLabelNames.size() )
922     {
923         return maLabelNames[nDim+1];
924     }
925     else
926         return OUString();
927 }
928 
PostInit()929 void ScDPCache::PostInit()
930 {
931     OSL_ENSURE(!maFields.empty(), "Cache not initialized!");
932 
933     maEmptyRows.build_tree();
934     auto it = maEmptyRows.rbegin();
935     OSL_ENSURE(it != maEmptyRows.rend(), "corrupt flat_segment_tree instance!");
936     mnDataSize = maFields[0]->maData.size();
937     ++it; // Skip the first position.
938     OSL_ENSURE(it != maEmptyRows.rend(), "buggy version of flat_segment_tree is used.");
939     if (it->second)
940     {
941         SCROW nLastNonEmpty = it->first - 1;
942         if (nLastNonEmpty+1 < mnDataSize)
943             mnDataSize = nLastNonEmpty+1;
944     }
945 }
946 
Clear()947 void ScDPCache::Clear()
948 {
949     mnColumnCount = 0;
950     mnRowCount = 0;
951     maFields.clear();
952     maLabelNames.clear();
953     maGroupFields.clear();
954     maEmptyRows.clear();
955     maStringPools.clear();
956 }
957 
GetItemDataId(sal_uInt16 nDim,SCROW nRow,bool bRepeatIfEmpty) const958 SCROW ScDPCache::GetItemDataId(sal_uInt16 nDim, SCROW nRow, bool bRepeatIfEmpty) const
959 {
960     OSL_ENSURE(nDim < mnColumnCount, "ScDPTableDataCache::GetItemDataId ");
961 
962     const Field& rField = *maFields[nDim];
963     if (o3tl::make_unsigned(nRow) >= rField.maData.size())
964     {
965         // nRow is in the trailing empty rows area.
966         if (bRepeatIfEmpty)
967             nRow = rField.maData.size()-1; // Move to the last non-empty row.
968         else
969             // Return the last item, which should always be empty if the
970             // initialization has skipped trailing empty rows.
971             return rField.maItems.size()-1;
972 
973     }
974     else if (bRepeatIfEmpty)
975     {
976         while (nRow > 0 && rField.maItems[rField.maData[nRow]].IsEmpty())
977             --nRow;
978     }
979 
980     return rField.maData[nRow];
981 }
982 
GetItemDataById(tools::Long nDim,SCROW nId) const983 const ScDPItemData* ScDPCache::GetItemDataById(tools::Long nDim, SCROW nId) const
984 {
985     if (nDim < 0 || nId < 0)
986         return nullptr;
987 
988     size_t nSourceCount = maFields.size();
989     size_t nDimPos = static_cast<size_t>(nDim);
990     size_t nItemId = static_cast<size_t>(nId);
991     if (nDimPos < nSourceCount)
992     {
993         // source field.
994         const Field& rField = *maFields[nDimPos];
995         if (nItemId < rField.maItems.size())
996             return &rField.maItems[nItemId];
997 
998         if (!rField.mpGroup)
999             return nullptr;
1000 
1001         nItemId -= rField.maItems.size();
1002         const ScDPItemDataVec& rGI = rField.mpGroup->maItems;
1003         if (nItemId >= rGI.size())
1004             return nullptr;
1005 
1006         return &rGI[nItemId];
1007     }
1008 
1009     // Try group fields.
1010     nDimPos -= nSourceCount;
1011     if (nDimPos >= maGroupFields.size())
1012         return nullptr;
1013 
1014     const ScDPItemDataVec& rGI = maGroupFields[nDimPos]->maItems;
1015     if (nItemId >= rGI.size())
1016         return nullptr;
1017 
1018     return &rGI[nItemId];
1019 }
1020 
GetFieldCount() const1021 size_t ScDPCache::GetFieldCount() const
1022 {
1023     return maFields.size();
1024 }
1025 
GetGroupFieldCount() const1026 size_t ScDPCache::GetGroupFieldCount() const
1027 {
1028     return maGroupFields.size();
1029 }
1030 
GetRowCount() const1031 SCROW ScDPCache::GetRowCount() const
1032 {
1033     return mnRowCount;
1034 }
1035 
GetDataSize() const1036 SCROW ScDPCache::GetDataSize() const
1037 {
1038     OSL_ENSURE(mnDataSize <= GetRowCount(), "Data size should never be larger than the row count.");
1039     return mnDataSize >= 0 ? mnDataSize : 0;
1040 }
1041 
GetFieldIndexArray(size_t nDim) const1042 const ScDPCache::IndexArrayType* ScDPCache::GetFieldIndexArray( size_t nDim ) const
1043 {
1044     if (nDim >= maFields.size())
1045         return nullptr;
1046 
1047     return &maFields[nDim]->maData;
1048 }
1049 
GetDimMemberValues(SCCOL nDim) const1050 const ScDPCache::ScDPItemDataVec& ScDPCache::GetDimMemberValues(SCCOL nDim) const
1051 {
1052     OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," nDim < mnColumnCount ");
1053     return maFields.at(nDim)->maItems;
1054 }
1055 
GetNumberFormat(tools::Long nDim) const1056 sal_uInt32 ScDPCache::GetNumberFormat( tools::Long nDim ) const
1057 {
1058     if ( nDim >= mnColumnCount )
1059         return 0;
1060 
1061     // TODO: Find a way to determine the dominant number format in presence of
1062     // multiple number formats in the same field.
1063     return maFields[nDim]->mnNumFormat;
1064 }
1065 
IsDateDimension(tools::Long nDim) const1066 bool ScDPCache::IsDateDimension( tools::Long nDim ) const
1067 {
1068     if (nDim >= mnColumnCount)
1069         return false;
1070 
1071     ScInterpreterContext& rContext = mrDoc.GetNonThreadedContext();
1072     SvNumFormatType eType = rContext.NFGetType(maFields[nDim]->mnNumFormat);
1073     return (eType == SvNumFormatType::DATE) || (eType == SvNumFormatType::DATETIME);
1074 }
1075 
GetDimMemberCount(tools::Long nDim) const1076 tools::Long ScDPCache::GetDimMemberCount(tools::Long nDim) const
1077 {
1078     OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," ScDPTableDataCache::GetDimMemberCount : out of bound ");
1079     return maFields[nDim]->maItems.size();
1080 }
1081 
GetDimensionIndex(std::u16string_view sName) const1082 SCCOL ScDPCache::GetDimensionIndex(std::u16string_view sName) const
1083 {
1084     for (size_t i = 1; i < maLabelNames.size(); ++i)
1085     {
1086         if (maLabelNames[i] == sName)
1087             return static_cast<SCCOL>(i-1);
1088     }
1089     return -1;
1090 }
1091 
InternString(size_t nDim,const OUString & rStr)1092 rtl_uString* ScDPCache::InternString( size_t nDim, const OUString& rStr )
1093 {
1094     assert(nDim < maStringPools.size());
1095     return internString(maStringPools[nDim], rStr);
1096 }
1097 
AddReference(ScDPObject * pObj) const1098 void ScDPCache::AddReference(ScDPObject* pObj) const
1099 {
1100     maRefObjects.insert(pObj);
1101 }
1102 
RemoveReference(ScDPObject * pObj) const1103 void ScDPCache::RemoveReference(ScDPObject* pObj) const
1104 {
1105     if (mbDisposing)
1106         // Object being deleted.
1107         return;
1108 
1109     maRefObjects.erase(pObj);
1110     if (maRefObjects.empty())
1111         mrDoc.GetDPCollection()->RemoveCache(this);
1112 }
1113 
GetAllReferences() const1114 const ScDPCache::ScDPObjectSet& ScDPCache::GetAllReferences() const
1115 {
1116     return maRefObjects;
1117 }
1118 
GetIdByItemData(tools::Long nDim,const ScDPItemData & rItem) const1119 SCROW ScDPCache::GetIdByItemData(tools::Long nDim, const ScDPItemData& rItem) const
1120 {
1121     if (nDim < 0)
1122         return -1;
1123 
1124     if (nDim < mnColumnCount)
1125     {
1126         // source field.
1127         const ScDPItemDataVec& rItems = maFields[nDim]->maItems;
1128         for (size_t i = 0, n = rItems.size(); i < n; ++i)
1129         {
1130             if (rItems[i] == rItem)
1131                 return i;
1132         }
1133 
1134         if (!maFields[nDim]->mpGroup)
1135             return -1;
1136 
1137         // grouped source field.
1138         const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
1139         for (size_t i = 0, n = rGI.size(); i < n; ++i)
1140         {
1141             if (rGI[i] == rItem)
1142                 return rItems.size() + i;
1143         }
1144         return -1;
1145     }
1146 
1147     // group field.
1148     nDim -= mnColumnCount;
1149     if (o3tl::make_unsigned(nDim) < maGroupFields.size())
1150     {
1151         const ScDPItemDataVec& rGI = maGroupFields[nDim]->maItems;
1152         for (size_t i = 0, n = rGI.size(); i < n; ++i)
1153         {
1154             if (rGI[i] == rItem)
1155                 return i;
1156         }
1157     }
1158 
1159     return -1;
1160 }
1161 
1162 // static
GetLocaleIndependentFormat(ScInterpreterContext & rContext,sal_uInt32 nNumFormat)1163 sal_uInt32 ScDPCache::GetLocaleIndependentFormat(ScInterpreterContext& rContext, sal_uInt32 nNumFormat)
1164 {
1165     // For a date or date+time format use ISO format so it works across locales
1166     // and can be matched against string based item queries. For time use 24h
1167     // format. All others use General format, no currency, percent, ...
1168     // Use en-US locale for all.
1169     switch (rContext.NFGetType(nNumFormat))
1170     {
1171         case SvNumFormatType::DATE:
1172             return rContext.NFGetFormatIndex( NF_DATE_ISO_YYYYMMDD, LANGUAGE_ENGLISH_US);
1173         case SvNumFormatType::TIME:
1174             return rContext.NFGetFormatIndex( NF_TIME_HHMMSS, LANGUAGE_ENGLISH_US);
1175         case SvNumFormatType::DATETIME:
1176             return rContext.NFGetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, LANGUAGE_ENGLISH_US);
1177         default:
1178             return rContext.NFGetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_ENGLISH_US);
1179     }
1180 }
1181 
1182 // static
GetLocaleIndependentFormattedNumberString(double fValue)1183 OUString ScDPCache::GetLocaleIndependentFormattedNumberString( double fValue )
1184 {
1185     return rtl::math::doubleToUString( fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
1186 }
1187 
1188 // static
GetLocaleIndependentFormattedString(double fValue,ScInterpreterContext & rContext,sal_uInt32 nNumFormat)1189 OUString ScDPCache::GetLocaleIndependentFormattedString( double fValue,
1190         ScInterpreterContext& rContext, sal_uInt32 nNumFormat )
1191 {
1192     nNumFormat = GetLocaleIndependentFormat( rContext, nNumFormat);
1193     if ((nNumFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
1194         return GetLocaleIndependentFormattedNumberString( fValue);
1195 
1196     OUString aStr;
1197     const Color* pColor = nullptr;
1198     rContext.NFGetOutputString( fValue, nNumFormat, aStr, &pColor);
1199     return aStr;
1200 }
1201 
GetFormattedString(tools::Long nDim,const ScDPItemData & rItem,bool bLocaleIndependent) const1202 OUString ScDPCache::GetFormattedString(tools::Long nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const
1203 {
1204     if (nDim < 0)
1205         return rItem.GetString();
1206 
1207     ScDPItemData::Type eType = rItem.GetType();
1208     if (eType == ScDPItemData::Value)
1209     {
1210         // Format value using the stored number format.
1211         ScInterpreterContext& rContext = mrDoc.GetNonThreadedContext();
1212         sal_uInt32 nNumFormat = GetNumberFormat(nDim);
1213         if (bLocaleIndependent)
1214             return GetLocaleIndependentFormattedString( rItem.GetValue(), rContext, nNumFormat);
1215 
1216         OUString aStr;
1217         const Color* pColor = nullptr;
1218         rContext.NFGetOutputString(rItem.GetValue(), nNumFormat, aStr, &pColor);
1219         return aStr;
1220     }
1221 
1222     if (eType == ScDPItemData::GroupValue)
1223     {
1224         ScDPItemData::GroupValueAttr aAttr = rItem.GetGroupValue();
1225         double fStart = 0.0, fEnd = 0.0;
1226         const GroupItems* p = GetGroupItems(nDim);
1227         if (p)
1228         {
1229             fStart = p->maInfo.mfStart;
1230             fEnd = p->maInfo.mfEnd;
1231         }
1232         return ScDPUtil::getDateGroupName(
1233             aAttr.mnGroupType, aAttr.mnValue, mrDoc.GetFormatTable(), fStart, fEnd);
1234     }
1235 
1236     if (eType == ScDPItemData::RangeStart)
1237     {
1238         double fVal = rItem.GetValue();
1239         const GroupItems* p = GetGroupItems(nDim);
1240         if (!p)
1241             return rItem.GetString();
1242 
1243         sal_Unicode cDecSep = ScGlobal::getLocaleData().getNumDecimalSep()[0];
1244         return ScDPUtil::getNumGroupName(fVal, p->maInfo, cDecSep, mrDoc.GetFormatTable());
1245     }
1246 
1247     return rItem.GetString();
1248 }
1249 
GetInterpreterContext() const1250 ScInterpreterContext& ScDPCache::GetInterpreterContext() const
1251 {
1252     return mrDoc.GetNonThreadedContext();
1253 }
1254 
AppendGroupField()1255 tools::Long ScDPCache::AppendGroupField()
1256 {
1257     maGroupFields.push_back(std::make_unique<GroupItems>());
1258     return static_cast<tools::Long>(maFields.size() + maGroupFields.size() - 1);
1259 }
1260 
ResetGroupItems(tools::Long nDim,const ScDPNumGroupInfo & rNumInfo,sal_Int32 nGroupType)1261 void ScDPCache::ResetGroupItems(tools::Long nDim, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nGroupType)
1262 {
1263     if (nDim < 0)
1264         return;
1265 
1266     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
1267     if (nDim < nSourceCount)
1268     {
1269         maFields.at(nDim)->mpGroup.reset(new GroupItems(rNumInfo, nGroupType));
1270         return;
1271     }
1272 
1273     nDim -= nSourceCount;
1274     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
1275     {
1276         GroupItems& rGI = *maGroupFields[nDim];
1277         rGI.maItems.clear();
1278         rGI.maInfo = rNumInfo;
1279         rGI.mnGroupType = nGroupType;
1280     }
1281 }
1282 
SetGroupItem(tools::Long nDim,const ScDPItemData & rData)1283 SCROW ScDPCache::SetGroupItem(tools::Long nDim, const ScDPItemData& rData)
1284 {
1285     if (nDim < 0)
1286         return -1;
1287 
1288     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
1289     if (nDim < nSourceCount)
1290     {
1291         GroupItems& rGI = *maFields.at(nDim)->mpGroup;
1292         rGI.maItems.push_back(rData);
1293         SCROW nId = maFields[nDim]->maItems.size() + rGI.maItems.size() - 1;
1294         return nId;
1295     }
1296 
1297     nDim -= nSourceCount;
1298     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
1299     {
1300         ScDPItemDataVec& rItems = maGroupFields.at(nDim)->maItems;
1301         rItems.push_back(rData);
1302         return rItems.size()-1;
1303     }
1304 
1305     return -1;
1306 }
1307 
GetGroupDimMemberIds(tools::Long nDim,std::vector<SCROW> & rIds) const1308 void ScDPCache::GetGroupDimMemberIds(tools::Long nDim, std::vector<SCROW>& rIds) const
1309 {
1310     if (nDim < 0)
1311         return;
1312 
1313     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
1314     if (nDim < nSourceCount)
1315     {
1316         if (!maFields.at(nDim)->mpGroup)
1317             return;
1318 
1319         size_t nOffset = maFields[nDim]->maItems.size();
1320         const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
1321         for (size_t i = 0, n = rGI.size(); i < n; ++i)
1322             rIds.push_back(static_cast<SCROW>(i + nOffset));
1323 
1324         return;
1325     }
1326 
1327     nDim -= nSourceCount;
1328     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
1329     {
1330         const ScDPItemDataVec& rGI = maGroupFields.at(nDim)->maItems;
1331         for (size_t i = 0, n = rGI.size(); i < n; ++i)
1332             rIds.push_back(static_cast<SCROW>(i));
1333     }
1334 }
1335 
1336 namespace {
1337 
1338 struct ClearGroupItems
1339 {
operator ()__anon5fd34f840511::ClearGroupItems1340     void operator() (const std::unique_ptr<ScDPCache::Field>& r) const
1341     {
1342         r->mpGroup.reset();
1343     }
1344 };
1345 
1346 }
1347 
ClearGroupFields()1348 void ScDPCache::ClearGroupFields()
1349 {
1350     maGroupFields.clear();
1351 }
1352 
ClearAllFields()1353 void ScDPCache::ClearAllFields()
1354 {
1355     ClearGroupFields();
1356     std::for_each(maFields.begin(), maFields.end(), ClearGroupItems());
1357 }
1358 
GetNumGroupInfo(tools::Long nDim) const1359 const ScDPNumGroupInfo* ScDPCache::GetNumGroupInfo(tools::Long nDim) const
1360 {
1361     if (nDim < 0)
1362         return nullptr;
1363 
1364     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
1365     if (nDim < nSourceCount)
1366     {
1367         if (!maFields.at(nDim)->mpGroup)
1368             return nullptr;
1369 
1370         return &maFields[nDim]->mpGroup->maInfo;
1371     }
1372 
1373     nDim -= nSourceCount;
1374     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
1375         return &maGroupFields.at(nDim)->maInfo;
1376 
1377     return nullptr;
1378 }
1379 
GetGroupType(tools::Long nDim) const1380 sal_Int32 ScDPCache::GetGroupType(tools::Long nDim) const
1381 {
1382     if (nDim < 0)
1383         return 0;
1384 
1385     tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
1386     if (nDim < nSourceCount)
1387     {
1388         if (!maFields.at(nDim)->mpGroup)
1389             return 0;
1390 
1391         return maFields[nDim]->mpGroup->mnGroupType;
1392     }
1393 
1394     nDim -= nSourceCount;
1395     if (nDim < static_cast<tools::Long>(maGroupFields.size()))
1396         return maGroupFields.at(nDim)->mnGroupType;
1397 
1398     return 0;
1399 }
1400 
1401 #if DUMP_PIVOT_TABLE
1402 
1403 namespace {
1404 
dumpItems(const ScDPCache & rCache,tools::Long nDim,const ScDPCache::ScDPItemDataVec & rItems,size_t nOffset)1405 void dumpItems(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, size_t nOffset)
1406 {
1407     for (size_t i = 0; i < rItems.size(); ++i)
1408         cout << "      " << (i+nOffset) << ": " << rCache.GetFormattedString(nDim, rItems[i], false) << endl;
1409 }
1410 
dumpSourceData(const ScDPCache & rCache,tools::Long nDim,const ScDPCache::ScDPItemDataVec & rItems,const ScDPCache::IndexArrayType & rArray)1411 void dumpSourceData(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, const ScDPCache::IndexArrayType& rArray)
1412 {
1413     for (const auto& rIndex : rArray)
1414         cout << "      '" << rCache.GetFormattedString(nDim, rItems[rIndex], false) << "'" << endl;
1415 }
1416 
getGroupTypeName(sal_Int32 nType)1417 const char* getGroupTypeName(sal_Int32 nType)
1418 {
1419     static const char* pNames[] = {
1420         "", "years", "quarters", "months", "days", "hours", "minutes", "seconds"
1421     };
1422 
1423     switch (nType)
1424     {
1425         case sheet::DataPilotFieldGroupBy::YEARS:    return pNames[1];
1426         case sheet::DataPilotFieldGroupBy::QUARTERS: return pNames[2];
1427         case sheet::DataPilotFieldGroupBy::MONTHS:   return pNames[3];
1428         case sheet::DataPilotFieldGroupBy::DAYS:     return pNames[4];
1429         case sheet::DataPilotFieldGroupBy::HOURS:    return pNames[5];
1430         case sheet::DataPilotFieldGroupBy::MINUTES:  return pNames[6];
1431         case sheet::DataPilotFieldGroupBy::SECONDS:  return pNames[7];
1432         default:
1433             ;
1434     }
1435 
1436     return pNames[0];
1437 }
1438 
1439 }
1440 
Dump() const1441 void ScDPCache::Dump() const
1442 {
1443     // Change these flags to fit your debugging needs.
1444     bool bDumpItems = false;
1445     bool bDumpSourceData = false;
1446 
1447     cout << "--- pivot cache dump" << endl;
1448     {
1449         size_t i = 0;
1450         for (const auto& rxField : maFields)
1451         {
1452             const Field& fld = *rxField;
1453             cout << "* source dimension: " << GetDimensionName(i) << " (ID = " << i << ")" << endl;
1454             cout << "    item count: " << fld.maItems.size() << endl;
1455             if (bDumpItems)
1456                 dumpItems(*this, i, fld.maItems, 0);
1457             if (fld.mpGroup)
1458             {
1459                 cout << "    group item count: " << fld.mpGroup->maItems.size() << endl;
1460                 cout << "    group type: " << getGroupTypeName(fld.mpGroup->mnGroupType) << endl;
1461                 if (bDumpItems)
1462                     dumpItems(*this, i, fld.mpGroup->maItems, fld.maItems.size());
1463             }
1464 
1465             if (bDumpSourceData)
1466             {
1467                 cout << "    source data (re-constructed):" << endl;
1468                 dumpSourceData(*this, i, fld.maItems, fld.maData);
1469             }
1470 
1471             ++i;
1472         }
1473     }
1474 
1475     {
1476         size_t i = maFields.size();
1477         for (const auto& rxGroupField : maGroupFields)
1478         {
1479             const GroupItems& gi = *rxGroupField;
1480             cout << "* group dimension: (unnamed) (ID = " << i << ")" << endl;
1481             cout << "    item count: " << gi.maItems.size() << endl;
1482             cout << "    group type: " << getGroupTypeName(gi.mnGroupType) << endl;
1483             if (bDumpItems)
1484                 dumpItems(*this, i, gi.maItems, 0);
1485             ++i;
1486         }
1487     }
1488 
1489     {
1490         struct { SCROW start; SCROW end; bool empty; } aRange;
1491         cout << "* empty rows: " << endl;
1492         mdds::flat_segment_tree<SCROW, bool>::const_iterator it = maEmptyRows.begin(), itEnd = maEmptyRows.end();
1493         if (it != itEnd)
1494         {
1495             aRange.start = it->first;
1496             aRange.empty = it->second;
1497 
1498             for (++it; it != itEnd; ++it)
1499             {
1500                 aRange.end = it->first-1;
1501                 cout << "    rows " << aRange.start << "-" << aRange.end << ": " << (aRange.empty ? "empty" : "not-empty") << endl;
1502                 aRange.start = it->first;
1503                 aRange.empty = it->second;
1504             }
1505         }
1506     }
1507 
1508     cout << "---" << endl;
1509 }
1510 
1511 #endif
1512 
1513 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1514