xref: /core/sc/source/core/data/dpfilteredcache.cxx (revision f528fff9)
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 #include <dpfilteredcache.hxx>
22 #include <address.hxx>
23 #include <queryparam.hxx>
24 #include <dpitemdata.hxx>
25 #include <com/sun/star/uno/Sequence.hxx>
26 #include <o3tl/safeint.hxx>
27 #include <osl/diagnose.h>
28 #include <algorithm>
29 
30 using ::std::vector;
31 using ::com::sun::star::uno::Sequence;
32 using ::com::sun::star::uno::Any;
33 
SingleFilter(const ScDPItemData & rItem)34 ScDPFilteredCache::SingleFilter::SingleFilter(const ScDPItemData& rItem) :
35     maItem(rItem) {}
36 
match(const ScDPItemData & rCellData) const37 bool ScDPFilteredCache::SingleFilter::match(const ScDPItemData& rCellData) const
38 {
39     return maItem == rCellData;
40 }
41 
getMatchValues() const42 std::vector<ScDPItemData> ScDPFilteredCache::SingleFilter::getMatchValues() const
43 {
44     return { maItem };
45 }
46 
GroupFilter()47 ScDPFilteredCache::GroupFilter::GroupFilter()
48 {
49 }
50 
match(const ScDPItemData & rCellData) const51 bool ScDPFilteredCache::GroupFilter::match(const ScDPItemData& rCellData) const
52 {
53     return std::find(maItems.begin(), maItems.end(), rCellData) != maItems.end();
54 }
55 
getMatchValues() const56 std::vector<ScDPItemData> ScDPFilteredCache::GroupFilter::getMatchValues() const
57 {
58     return maItems;
59 }
60 
addMatchItem(const ScDPItemData & rItem)61 void ScDPFilteredCache::GroupFilter::addMatchItem(const ScDPItemData& rItem)
62 {
63     maItems.push_back(rItem);
64 }
65 
getMatchItemCount() const66 size_t ScDPFilteredCache::GroupFilter::getMatchItemCount() const
67 {
68     return maItems.size();
69 }
70 
Criterion()71 ScDPFilteredCache::Criterion::Criterion() :
72     mnFieldIndex(-1)
73 {
74 }
75 
ScDPFilteredCache(const ScDPCache & rCache)76 ScDPFilteredCache::ScDPFilteredCache(const ScDPCache& rCache) :
77     maShowByFilter(0, MAXROW+1, false), maShowByPage(0, MAXROW+1, true), mrCache(rCache)
78 {
79 }
80 
~ScDPFilteredCache()81 ScDPFilteredCache::~ScDPFilteredCache()
82 {
83 }
84 
getRowSize() const85 sal_Int32 ScDPFilteredCache::getRowSize() const
86 {
87     return mrCache.GetRowCount();
88 }
89 
getColSize() const90 sal_Int32 ScDPFilteredCache::getColSize() const
91 {
92     return mrCache.GetColumnCount();
93 }
94 
fillTable(const ScQueryParam & rQuery,bool bIgnoreEmptyRows,bool bRepeatIfEmpty)95 void ScDPFilteredCache::fillTable(
96     const ScQueryParam& rQuery, bool bIgnoreEmptyRows, bool bRepeatIfEmpty)
97 {
98     SCROW nRowCount = getRowSize();
99     SCROW nDataSize = mrCache.GetDataSize();
100     SCCOL nColCount = getColSize();
101     if (nRowCount <= 0 || nColCount <= 0)
102         return;
103 
104     maShowByFilter.clear();
105     maShowByPage.clear();
106     maShowByPage.build_tree();
107 
108     // Process the non-empty data rows.
109     for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
110     {
111         if (!getCache().ValidQuery(nRow, rQuery))
112             continue;
113 
114         if (bIgnoreEmptyRows && getCache().IsRowEmpty(nRow))
115             continue;
116 
117         maShowByFilter.insert_back(nRow, nRow+1, true);
118     }
119 
120     // Process the trailing empty rows.
121     if (!bIgnoreEmptyRows)
122         maShowByFilter.insert_back(nDataSize, nRowCount, true);
123 
124     maShowByFilter.build_tree();
125 
126     // Initialize field entries container.
127     maFieldEntries.clear();
128     maFieldEntries.reserve(nColCount);
129 
130     // Build unique field entries.
131     for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
132     {
133         maFieldEntries.emplace_back( );
134         SCROW nMemCount = getCache().GetDimMemberCount( nCol );
135         if (!nMemCount)
136             continue;
137 
138         std::vector<SCROW> aAdded(nMemCount, -1);
139         bool bShow = false;
140         SCROW nEndSegment = -1;
141         for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
142         {
143             if (nRow > nEndSegment)
144             {
145                 if (!maShowByFilter.search_tree(nRow, bShow, nullptr, &nEndSegment).second)
146                 {
147                     OSL_FAIL("Tree search failed!");
148                     continue;
149                 }
150                 --nEndSegment; // End position is not inclusive. Move back one.
151             }
152 
153             if (!bShow)
154             {
155                 nRow = nEndSegment;
156                 continue;
157             }
158 
159             SCROW nIndex = getCache().GetItemDataId(nCol, nRow, bRepeatIfEmpty);
160             aAdded[nIndex] = nIndex;
161 
162             // tdf#96588 - large numbers of trailing identical empty
163             // rows generate the same nIndex & nOrder.
164             if (nRow == nDataSize)
165                 break;
166         }
167         for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
168         {
169             if (aAdded[nRow] != -1)
170                 maFieldEntries.back().push_back(aAdded[nRow]);
171         }
172     }
173 }
174 
fillTable()175 void ScDPFilteredCache::fillTable()
176 {
177     SCROW nRowCount = getRowSize();
178     SCCOL nColCount = getColSize();
179     if (nRowCount <= 0 || nColCount <= 0)
180         return;
181 
182     maShowByPage.clear();
183     maShowByPage.build_tree();
184 
185     maShowByFilter.clear();
186     maShowByFilter.insert_front(0, nRowCount, true);
187     maShowByFilter.build_tree();
188 
189     // Initialize field entries container.
190     maFieldEntries.clear();
191     maFieldEntries.reserve(nColCount);
192 
193     // Data rows
194     for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
195     {
196         maFieldEntries.emplace_back( );
197         SCROW nMemCount = getCache().GetDimMemberCount( nCol );
198         if (!nMemCount)
199             continue;
200 
201         std::vector<SCROW> aAdded(nMemCount, -1);
202 
203         for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
204         {
205             SCROW nIndex = getCache().GetItemDataId(nCol, nRow, false);
206             aAdded[nIndex] = nIndex;
207         }
208         for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
209         {
210             if (aAdded[nRow] != -1)
211                 maFieldEntries.back().push_back(aAdded[nRow]);
212         }
213     }
214 }
215 
isRowActive(sal_Int32 nRow,sal_Int32 * pLastRow) const216 bool ScDPFilteredCache::isRowActive(sal_Int32 nRow, sal_Int32* pLastRow) const
217 {
218     bool bFilter = false, bPage = true;
219     SCROW nLastRowFilter = MAXROW, nLastRowPage = MAXROW;
220     maShowByFilter.search_tree(nRow, bFilter, nullptr, &nLastRowFilter);
221     maShowByPage.search_tree(nRow, bPage, nullptr, &nLastRowPage);
222     if (pLastRow)
223     {
224         // Return the last row of current segment.
225         *pLastRow = std::min(nLastRowFilter, nLastRowPage);
226         *pLastRow -= 1; // End position is not inclusive. Move back one.
227     }
228 
229     return bFilter && bPage;
230 }
231 
filterByPageDimension(const vector<Criterion> & rCriteria,const std::unordered_set<sal_Int32> & rRepeatIfEmptyDims)232 void ScDPFilteredCache::filterByPageDimension(const vector<Criterion>& rCriteria, const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
233 {
234     SCROW nRowSize = getRowSize();
235     SCROW nDataSize = mrCache.GetDataSize();
236 
237     maShowByPage.clear();
238 
239     for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
240     {
241         bool bShow = isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims);
242         maShowByPage.insert_back(nRow, nRow+1, bShow);
243     }
244 
245     // tdf#96588 - rapidly extend for blank rows with identical data
246     if (nDataSize < nRowSize)
247     {
248         bool bBlankShow = isRowQualified(nDataSize, rCriteria, rRepeatIfEmptyDims);
249         maShowByPage.insert_back(nDataSize, nRowSize, bBlankShow);
250     }
251 
252     maShowByPage.build_tree();
253 }
254 
getCell(SCCOL nCol,SCROW nRow,bool bRepeatIfEmpty) const255 const ScDPItemData* ScDPFilteredCache::getCell(SCCOL nCol, SCROW nRow, bool bRepeatIfEmpty) const
256 {
257    SCROW nId= mrCache.GetItemDataId(nCol, nRow, bRepeatIfEmpty);
258    return mrCache.GetItemDataById( nCol, nId );
259 }
260 
getValue(ScDPValue & rVal,SCCOL nCol,SCROW nRow) const261 void  ScDPFilteredCache::getValue( ScDPValue& rVal, SCCOL nCol, SCROW nRow) const
262 {
263     const ScDPItemData* pData = getCell( nCol, nRow, false/*bRepeatIfEmpty*/ );
264 
265     if (pData)
266     {
267         rVal.mfValue = pData->IsValue() ? pData->GetValue() : 0.0;
268         rVal.meType = pData->GetCellType();
269     }
270     else
271         rVal.Set(0.0, ScDPValue::Empty);
272 }
273 
getFieldName(SCCOL nIndex) const274 OUString ScDPFilteredCache::getFieldName(SCCOL nIndex) const
275 {
276     return mrCache.GetDimensionName(nIndex);
277 }
278 
getFieldEntries(sal_Int32 nColumn) const279 const ::std::vector<SCROW>&  ScDPFilteredCache::getFieldEntries( sal_Int32 nColumn ) const
280 {
281     if (nColumn < 0 || o3tl::make_unsigned(nColumn) >= maFieldEntries.size())
282     {
283         // index out of bound.  Hopefully this code will never be reached.
284         static const ::std::vector<SCROW> emptyEntries{};
285         return emptyEntries;
286     }
287     return maFieldEntries[nColumn];
288 }
289 
filterTable(const vector<Criterion> & rCriteria,Sequence<Sequence<Any>> & rTabData,const std::unordered_set<sal_Int32> & rRepeatIfEmptyDims)290 void ScDPFilteredCache::filterTable(const vector<Criterion>& rCriteria, Sequence< Sequence<Any> >& rTabData,
291                                  const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
292 {
293     sal_Int32 nRowSize = getRowSize();
294     SCCOL nColSize = getColSize();
295 
296     if (!nRowSize)
297         // no data to filter.
298         return;
299 
300     // Row first, then column.
301     vector< Sequence<Any> > tableData;
302     tableData.reserve(nRowSize+1);
303 
304     // Header first.
305     Sequence<Any> headerRow(nColSize);
306     auto pRow = headerRow.getArray();
307     for (SCCOL  nCol = 0; nCol < nColSize; ++nCol)
308     {
309         OUString str = getFieldName( nCol);
310         Any any;
311         any <<= str;
312         pRow[nCol] = any;
313     }
314     tableData.push_back(headerRow);
315 
316     for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
317     {
318         sal_Int32 nLastRow;
319         if (!isRowActive(nRow, &nLastRow))
320         {
321             // This row is filtered out.
322             nRow = nLastRow;
323             continue;
324         }
325 
326         if (!isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims))
327             continue;
328 
329         // Insert this row into table.
330 
331         Sequence<Any> row(nColSize);
332         pRow = row.getArray();
333         for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
334         {
335             Any any;
336             bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(nCol) > 0;
337             const ScDPItemData* pData= getCell(nCol, nRow, bRepeatIfEmpty);
338             if ( pData->IsValue() )
339                 any <<= pData->GetValue();
340             else
341             {
342                   OUString string (pData->GetString() );
343                   any <<= string;
344             }
345             pRow[nCol] = any;
346         }
347         tableData.push_back(row);
348     }
349 
350     // convert vector to Sequence
351     sal_Int32 nTabSize = static_cast<sal_Int32>(tableData.size());
352     rTabData.realloc(nTabSize);
353     auto pTabData = rTabData.getArray();
354     for (sal_Int32 i = 0; i < nTabSize; ++i)
355         pTabData[i] = tableData[i];
356 }
357 
clear()358 void ScDPFilteredCache::clear()
359 {
360     maFieldEntries.clear();
361     maShowByFilter.clear();
362     maShowByPage.clear();
363 }
364 
empty() const365 bool ScDPFilteredCache::empty() const
366 {
367     return maFieldEntries.empty();
368 }
369 
isRowQualified(sal_Int32 nRow,const vector<Criterion> & rCriteria,const std::unordered_set<sal_Int32> & rRepeatIfEmptyDims) const370 bool ScDPFilteredCache::isRowQualified(sal_Int32 nRow, const vector<Criterion>& rCriteria,
371                                     const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims) const
372 {
373     sal_Int32 nColSize = getColSize();
374     for (const auto& rCriterion : rCriteria)
375     {
376         if (rCriterion.mnFieldIndex >= nColSize)
377             // specified field is outside the source data columns.  Don't
378             // use this criterion.
379             continue;
380 
381         // Check if the 'repeat if empty' flag is set for this field.
382         bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(rCriterion.mnFieldIndex) > 0;
383         const ScDPItemData* pCellData = getCell(static_cast<SCCOL>(rCriterion.mnFieldIndex), nRow, bRepeatIfEmpty);
384         if (!rCriterion.mpFilter->match(*pCellData))
385             return false;
386     }
387     return true;
388 }
389 
390 #if DUMP_PIVOT_TABLE
391 
dumpRowFlag(const RowFlagType & rFlag)392 void ScDPFilteredCache::dumpRowFlag( const RowFlagType& rFlag )
393 {
394     RowFlagType::const_iterator it = rFlag.begin(), itEnd = rFlag.end();
395     bool bShow = it->second;
396     SCROW nRow1 = it->first;
397     for (++it; it != itEnd; ++it)
398     {
399         SCROW nRow2 = it->first;
400         cout << "  * range " << nRow1 << "-" << nRow2 << ": " << (bShow ? "on" : "off") << endl;
401         bShow = it->second;
402         nRow1 = nRow2;
403     }
404 }
405 
dump() const406 void ScDPFilteredCache::dump() const
407 {
408     cout << "--- pivot filtered cache dump" << endl;
409 
410     cout << endl;
411     cout << "* show by filter" << endl;
412     dumpRowFlag(maShowByFilter);
413 
414     cout << endl;
415     cout << "* show by page dimensions" << endl;
416     dumpRowFlag(maShowByPage);
417 
418     cout << endl;
419     cout << "* field entries" << endl;
420     size_t nFieldCount = maFieldEntries.size();
421     for (size_t i = 0; i < nFieldCount; ++i)
422     {
423         const vector<SCROW>& rField = maFieldEntries[i];
424         cout << "  * field " << i << endl;
425         for (size_t j = 0, n = rField.size(); j < n; ++j)
426             cout << "    ID: " << rField[j] << endl;
427     }
428     cout << "---" << endl;
429 }
430 
431 #endif
432 
433 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
434