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