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