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