sc/qa/unit/ucalc.hxx             |    1 
 sc/qa/unit/ucalc_formula.cxx     |   71 +++++++++++++++++++++++++++++++++++++++
 sc/source/core/tool/compiler.cxx |   29 +++++++++++++++
 3 files changed, 101 insertions(+)

New commits:
commit aaf14013a3fa97147f78159361d8b1148b91f5f4
Author:     Balazs Varga <balazs.varga.ext...@allotropia.de>
AuthorDate: Mon Jan 27 09:57:25 2025 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Mon Feb 3 17:20:16 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.
    
    cherry-pick from: 66eecde324aa1687ececc3f0f514358a7f69bc75
    
    Change-Id: I9340b520cd3ac333b63b4aec35a3c647f5d9a119
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180974
    Tested-by: allotropia jenkins <jenk...@allotropia.de>
    Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de>

diff --git a/sc/qa/unit/ucalc.hxx b/sc/qa/unit/ucalc.hxx
index 9852a16e7eb6..f89076770fb2 100644
--- a/sc/qa/unit/ucalc.hxx
+++ b/sc/qa/unit/ucalc.hxx
@@ -189,6 +189,7 @@ public:
     void testFuncSUM();
     void testFuncPRODUCT();
     void testFuncSUMPRODUCT();
+    void testFuncSUBTOTAL();
     void testFuncSUMXMY2();
     void testFuncMIN();
     void testFuncN();
diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx
index 1575731f56e7..1a81aa8abf74 100644
--- a/sc/qa/unit/ucalc_formula.cxx
+++ b/sc/qa/unit/ucalc_formula.cxx
@@ -4685,6 +4685,77 @@ void Test::testFuncSUMPRODUCT()
     m_pDoc->DeleteTab(0);
 }
 
+void Test::testFuncSUBTOTAL()
+{
+    m_pDoc->InsertTab(0, "Formula");
+
+    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, "MyRelative", "$C1:$C$1000", 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), 
"=IF(SUBTOTAL(3;MyRelative)=1;"";SUBTOTAL(3;MyRelative))");
+
+    // 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(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);
+}
+
 void Test::testFuncSUMXMY2()
 {
     m_pDoc->InsertTab(0, "Test SumXMY2");
diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx
index 1a6833204f0b..a994f5cf1ebf 100644
--- a/sc/source/core/tool/compiler.cxx
+++ b/sc/source/core/tool/compiler.cxx
@@ -6439,6 +6439,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(aPos);
+                SCCOL nTempStartCol = rRange.aStart.Col();
+                SCROW nTempStartRow = rRange.aStart.Row();
+                SCCOL nTempEndCol = rRange.aEnd.Col();
+                SCROW nTempEndRow = rRange.aEnd.Row();
+                pDoc->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(rRange, aPos);
+            }
+            --ppTok;
+        }
+    }
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to