sc/qa/unit/ucalc_formula.cxx     |   72 +++++++++++++++++++++++++++++++++++++++
 sc/source/core/tool/compiler.cxx |   29 +++++++++++++++
 2 files changed, 101 insertions(+)

New commits:
commit b89047a0f0100fb30121084cf42815aa792c1f88
Author:     Balazs Varga <balazs.varga.ext...@allotropia.de>
AuthorDate: Mon Jan 27 09:57:25 2025 +0100
Commit:     Balazs Varga <balazs.varga.ext...@allotropia.de>
CommitDate: Mon Feb 3 12:54:01 2025 +0100

    tdf#164843 - sc optimize "SubTotal" function's reference ranges
    
    Double references from relative named ranges can point to large
    ranges (MAXCOL/MAXROW) and because of that some function evaluation
    like SubTotal can be extreamly slow when we call 
ScTable::CompileHybridFormula
    with these large ranges. Since all the SubTotal functions ignore empty cells
    its worth to optimize and trim the double references in SubTotal functions.
    
    Change-Id: I9340b520cd3ac333b63b4aec35a3c647f5d9a119
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180773
    Tested-by: Gabor Kelemen <gabor.kelemen.ext...@allotropia.de>
    Tested-by: Jenkins
    Reviewed-by: Gabor Kelemen <gabor.kelemen.ext...@allotropia.de>

diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx
index 40d2c6f174de..4dd944fb1774 100644
--- a/sc/qa/unit/ucalc_formula.cxx
+++ b/sc/qa/unit/ucalc_formula.cxx
@@ -4761,6 +4761,78 @@ CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMPRODUCT)
     m_pDoc->DeleteTab(0);
 }
 
+CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUBTOTAL)
+{
+    m_pDoc->InsertTab(0, u"Formula"_ustr);
+
+    sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on.
+
+    // Fill C1:C1025 with values and insert formulas in D1:D1025 using the 
named expression.
+    for (size_t i = 0; i < 1025; i++)
+        m_pDoc->SetValue(ScAddress(2, i, 0), i + 1);
+
+    // Add a named expression for a function.
+    ScRangeName* pGlobalNames = m_pDoc->GetRangeName();
+    CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", 
pGlobalNames);
+    ScRangeData* pName = new ScRangeData(
+        *m_pDoc, u"MyRelative"_ustr, u"$C1:$C$1000"_ustr, ScAddress(2, 999, 0),
+        ScRangeData::Type::Name, formula::FormulaGrammar::GRAM_NATIVE);
+
+    bool bInserted = pGlobalNames->insert(pName);
+    CPPUNIT_ASSERT_MESSAGE("Failed to insert a new name.", bInserted);
+
+    for (size_t i = 0; i < 1025; i++)
+        m_pDoc->SetString(ScAddress(3, i, 0), 
u"=IF(SUBTOTAL(3;MyRelative)=1;"";SUBTOTAL(3;MyRelative))"_ustr);
+
+    // Make sure the results are correct.
+    for (size_t i = 0; i < 1025; i++)
+    {
+        if (i < 999.0)
+            CPPUNIT_ASSERT_EQUAL(26.0, m_pDoc->GetValue(3, i, 0));
+        else
+            CPPUNIT_ASSERT_EQUAL((1000.0 - (i - 999.0)), m_pDoc->GetValue(3, 
i, 0));
+    }
+
+    ScRange aTrimedRange(2, 999, 0, 2, 1024, 0);
+    ScRange aValidRange(2, 0, 0, 2, 999, 0);
+    for (size_t i = 0; i < 1025; i++)
+    {
+        ScFormulaCell* pCell = m_pDoc->GetFormulaCell(ScAddress(3, i, 0));
+        ScTokenArray* pCode = pCell->GetCode();
+        sal_uInt16 nLen = pCode->GetCodeLen();
+        FormulaToken** pRPNArray = pCode->GetCode();
+        OUString aCellName = pCell->aPos.GetColRowString();
+
+        for (sal_uInt16 nIdx = 0; nIdx < nLen; ++nIdx)
+        {
+            FormulaToken* pTok = pRPNArray[nIdx];
+            if (pTok && pTok->GetType() == svDoubleRef)
+            {
+                ScRange aRange = pTok->GetDoubleRef()->toAbs(*m_pDoc, 
ScAddress(3, i, 0));
+                if (i < 999)
+                {
+                    CPPUNIT_ASSERT_EQUAL_MESSAGE(OUString("Double ref is 
incorrectly trimmed in: " + aCellName).toUtf8().getStr(),
+                        aRange, aTrimedRange);
+                    // Without the trim it would failed with
+                    // assertion failed
+                    // - Expression: aRange == aTrimedRange
+                    // - Double ref is incorrectly trimmed in : D1
+                    // ScRange aTrimmableRange(2, 999, 0, 0, 1048575, 0);
+                }
+                else
+                {
+                    CPPUNIT_ASSERT_EQUAL_MESSAGE(OUString("Double ref is 
incorrectly trimmed in: " + aCellName).toUtf8().getStr(),
+                        aRange, aValidRange);
+                }
+            }
+        }
+        if (i >= 999)
+            aValidRange.aStart.IncRow();
+    }
+
+    m_pDoc->DeleteTab(0);
+}
+
 CPPUNIT_TEST_FIXTURE(TestFormula, testFuncSUMXMY2)
 {
     m_pDoc->InsertTab(0, u"Test SumXMY2"_ustr);
diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx
index 90f67c3eec22..07d59de2df16 100644
--- a/sc/source/core/tool/compiler.cxx
+++ b/sc/source/core/tool/compiler.cxx
@@ -6817,6 +6817,35 @@ void ScCompiler::AnnotateTrimOnDoubleRefs()
             --ppTok;
         }
     }
+    else if (eOpCode == ocSubTotal)
+    {
+        // tdf#164843: Double references from relative named ranges can point 
to large
+        // ranges (MAXCOL/MAXROW) and because of that some function evaluation
+        // like SubTotal can be extreamly slow when we call 
ScTable::CompileHybridFormula
+        // with these large ranges. Since all the SubTotal functions ignore 
empty cells
+        // its worth to optimize and trim the double references in SubTotal 
functions.
+        FormulaToken** ppTok = pCode - 2;
+        while (*ppTok)
+        {
+            FormulaToken* pTok = *ppTok;
+            if (pTok->GetType() == svDoubleRef)
+            {
+                ScComplexRefData* pRefData = pTok->GetDoubleRef();
+                // no need to set pRefData->SetTrimToData(true); because we 
already trim here
+                ScRange rRange = pRefData->toAbs(rDoc, aPos);
+                SCCOL nTempStartCol = rRange.aStart.Col();
+                SCROW nTempStartRow = rRange.aStart.Row();
+                SCCOL nTempEndCol = rRange.aEnd.Col();
+                SCROW nTempEndRow = rRange.aEnd.Row();
+                rDoc.ShrinkToDataArea(rRange.aStart.Tab(), nTempStartCol, 
nTempStartRow, nTempEndCol, nTempEndRow);
+                rRange.aStart.Set(nTempStartCol, nTempStartRow, 
rRange.aStart.Tab());
+                rRange.aEnd.Set(nTempEndCol, nTempEndRow, rRange.aEnd.Tab());
+                rRange.PutInOrder();
+                pRefData->SetRange(rDoc.GetSheetLimits(), rRange, aPos);
+            }
+            --ppTok;
+        }
+    }
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to