Selaa lähdekoodia

HPCC-9050 Allow ROLLUP condition to selectively use rolled up row for LEFT

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 12 vuotta sitten
vanhempi
commit
629d026be1

+ 83 - 4
ecl/hql/hqlpmap.cpp

@@ -1021,14 +1021,14 @@ bool transformReturnsSide(IHqlExpression * expr, node_operator side, unsigned in
     return isTrivialTransform(queryNewColumnProvider(expr), selector);
 }
 
-IHqlExpression * getExtractSelect(IHqlExpression * expr, IHqlExpression * field)
+IHqlExpression * getExtractSelect(IHqlExpression * transform, IHqlExpression * field)
 {
-    if (expr->getInfoFlags() & (HEFcontainsSkip))
+    if (transform->getInfoFlags() & (HEFcontainsSkip))
         return NULL;
 
-    ForEachChild(i, expr)
+    ForEachChild(i, transform)
     {
-        IHqlExpression * cur = expr->queryChild(i);
+        IHqlExpression * cur = transform->queryChild(i);
         switch (cur->getOperator())
         {
         case no_assignall:
@@ -1055,6 +1055,85 @@ IHqlExpression * getExtractSelect(IHqlExpression * expr, IHqlExpression * field)
 }
 
 
+IHqlExpression * getExtractSelect(IHqlExpression * transform, IHqlExpression * selector, IHqlExpression * select)
+{
+    if (select->getOperator() != no_select)
+        return NULL;
+    IHqlExpression * ds = select->queryChild(0);
+    IHqlExpression * field = select->queryChild(1);
+    if (ds == selector)
+        return getExtractSelect(transform, field);
+    OwnedHqlExpr extracted = getExtractSelect(transform, selector, ds);
+    if (!extracted)
+        return NULL;
+    if (extracted->getOperator() == no_createrow)
+        return getExtractSelect(extracted->queryChild(0), field);
+    return createSelectExpr(extracted.getClear(), LINK(field));
+}
+
+static HqlTransformerInfo selectorFieldAnalyserInfo("SelectorFieldAnalyser");
+class HQL_API SelectorFieldAnalyser : public NewHqlTransformer
+{
+public:
+    SelectorFieldAnalyser(HqlExprCopyArray & _selects, IHqlExpression * _selector) 
+    : NewHqlTransformer(selectorFieldAnalyserInfo), selects(_selects), selector(_selector)
+    {
+    }
+
+protected:
+    virtual void analyseExpr(IHqlExpression * expr);
+    virtual void analyseSelector(IHqlExpression * expr);
+
+    void checkMatch(IHqlExpression * select);
+
+protected:
+    LinkedHqlExpr selector;
+    HqlExprCopyArray & selects;
+};
+
+
+void SelectorFieldAnalyser::analyseExpr(IHqlExpression * expr)
+{
+    if (!expr->usesSelector(selector))
+        return;
+
+    if (expr->getOperator() == no_select)
+    {
+        if (!isNewSelector(expr))
+        {
+            checkMatch(expr);
+            return;
+        }
+    }
+    NewHqlTransformer::analyseExpr(expr);
+}
+
+
+void SelectorFieldAnalyser::analyseSelector(IHqlExpression * expr)
+{
+    if (expr->getOperator() == no_select)
+        checkMatch(expr);
+}
+
+
+void SelectorFieldAnalyser::checkMatch(IHqlExpression * select)
+{
+    assertex(select->getOperator() == no_select);
+    if (queryDatasetCursor(select) == selector)
+    {
+        if (!selects.contains(*select))
+            selects.append(*select);
+    }
+}
+
+extern HQL_API void gatherSelects(HqlExprCopyArray & selects, IHqlExpression * expr, IHqlExpression * selector)
+{
+    SelectorFieldAnalyser analyser(selects, selector);
+    analyser.analyse(expr, 0);
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+
 static IHqlExpression * getTrivialSelect(IHqlExpression * expr, IHqlExpression * selector, IHqlExpression * field)
 {
     OwnedHqlExpr match = getExtractSelect(expr, field);

+ 5 - 1
ecl/hql/hqlpmap.hpp

@@ -183,7 +183,11 @@ extern HQL_API bool transformReturnsSide(IHqlExpression * expr, node_operator si
 
 extern HQL_API bool sortDistributionMatches(IHqlExpression * dataset, bool isLocal);
 
-extern HQL_API IHqlExpression * getExtractSelect(IHqlExpression * expr, IHqlExpression * field);
+extern HQL_API IHqlExpression * getExtractSelect(IHqlExpression * transform, IHqlExpression * field);
+//Find the assignment that corresponds to select (with selector "selector") and return the assigned value
+extern HQL_API IHqlExpression * getExtractSelect(IHqlExpression * transform, IHqlExpression * selector, IHqlExpression * select);
 extern HQL_API IHqlExpression * getParentDatasetSelector(IHqlExpression * ds);
+//return all selects from the expression that refer to a particular selector
+extern HQL_API void gatherSelects(HqlExprCopyArray & selectors, IHqlExpression * expr, IHqlExpression * selector);
 
 #endif

+ 127 - 0
ecl/hql/hqlvalid.cpp

@@ -28,6 +28,7 @@
 #include "hqlutil.hpp"
 #include "hqlvalid.hpp"
 #include "hqlerror.hpp"
+#include "hqlpmap.hpp"
 
 MODULE_INIT(INIT_PRIORITY_STANDARD)
 {
@@ -153,3 +154,129 @@ IHqlExpression * checkCreateConcreteModule(IErrorReceiver * errors, IHqlExpressi
     ECLlocation location(locationExpr);
     return checkCreateConcreteModule(errors, expr, location);
 }
+
+//---------------------------------------------------------------------------------------------------------------------
+
+static bool matchesIfSelect(IHqlExpression * expr, IHqlExpression * leftSelect, IHqlExpression * rightSelect)
+{
+    if ((expr == leftSelect) || (expr == rightSelect))
+        return true;
+
+    node_operator op = expr->getOperator();
+    if (op == no_constant)
+        return true;
+    if (op == no_if)
+    {
+        if (matchesIfSelect(expr->queryChild(1), leftSelect, rightSelect) &&
+            matchesIfSelect(expr->queryChild(2), leftSelect, rightSelect))
+            return true;
+    }
+    if ((op == no_select) &&
+        (expr->queryChild(1) == leftSelect->queryChild(1)))
+        return matchesIfSelect(expr->queryChild(0), leftSelect->queryChild(0), rightSelect->queryChild(0));
+
+    return false;
+}
+
+IHqlExpression * queryAmbiguousRollupCondition(IHqlExpression * expr, bool strict)
+{
+    IHqlExpression * dataset = expr->queryChild(0);
+    IHqlExpression * transform = expr->queryChild(2);
+    OwnedHqlExpr left = createSelector(no_left, dataset, querySelSeq(expr));
+    OwnedHqlExpr right = createSelector(no_right, dataset, querySelSeq(expr));
+    IHqlExpression * selector = dataset->queryNormalizedSelector();
+
+    HqlExprCopyArray selects;
+    gatherSelects(selects, expr->queryChild(1), selector);
+    ForEachItemIn(i, selects)
+    {
+        IHqlExpression * select = &selects.item(i);
+        IHqlExpression * value = getExtractSelect(transform, selector, select);
+        if (!value)
+            return select;
+
+        if (value->getOperator() == no_constant)
+            continue;
+
+        OwnedHqlExpr leftSelect = replaceSelector(select, selector, left);
+        if (value == leftSelect)
+            continue;
+
+        OwnedHqlExpr rightSelect = replaceSelector(select, selector, right);
+        if (value == rightSelect)
+            continue;
+
+        if (strict)
+            return select;
+
+        if (!matchesIfSelect(value, leftSelect, rightSelect))
+            return select;
+    }
+    return NULL;
+}
+
+
+IHqlExpression * queryAmbiguousRollupCondition(IHqlExpression * expr, const HqlExprArray & selects)
+{
+    IHqlExpression * dataset = expr->queryChild(0);
+    IHqlExpression * transform = expr->queryChild(2);
+    OwnedHqlExpr left = createSelector(no_left, dataset, querySelSeq(expr));
+    OwnedHqlExpr right = createSelector(no_right, dataset, querySelSeq(expr));
+    IHqlExpression * selector = dataset->queryNormalizedSelector();
+
+    ForEachItemIn(i, selects)
+    {
+        IHqlExpression * select = &selects.item(i);
+        IHqlExpression * value = getExtractSelect(transform, selector, select);
+        if (!value)
+            return select;
+
+        if (value->getOperator() == no_constant)
+            continue;
+
+        OwnedHqlExpr leftSelect = replaceSelector(select, selector, left);
+        if (value == leftSelect)
+            continue;
+
+        OwnedHqlExpr rightSelect = replaceSelector(select, selector, right);
+        if (value == rightSelect)
+            continue;
+
+        return select;
+    }
+    return NULL;
+}
+
+
+void filterAmbiguousRollupCondition(HqlExprArray & ambiguousSelects, HqlExprArray & selects, IHqlExpression * expr)
+{
+    IHqlExpression * dataset = expr->queryChild(0);
+    IHqlExpression * transform = expr->queryChild(2);
+    OwnedHqlExpr left = createSelector(no_left, dataset, querySelSeq(expr));
+    OwnedHqlExpr right = createSelector(no_right, dataset, querySelSeq(expr));
+    IHqlExpression * selector = dataset->queryNormalizedSelector();
+
+    ForEachItemInRev(i, selects)
+    {
+        IHqlExpression * select = &selects.item(i);
+        IHqlExpression * value = getExtractSelect(transform, selector, select);
+        if (value)
+        {
+            if (value->getOperator() == no_constant)
+                continue;
+
+            OwnedHqlExpr leftSelect = replaceSelector(select, selector, left);
+            if (value == leftSelect)
+                continue;
+
+            OwnedHqlExpr rightSelect = replaceSelector(select, selector, right);
+            if (value == rightSelect)
+                continue;
+        }
+
+        ambiguousSelects.append(*LINK(select));
+        selects.remove(i);
+    }
+}
+
+

+ 4 - 0
ecl/hql/hqlvalid.hpp

@@ -24,4 +24,8 @@ IHqlExpression * checkCreateConcreteModule(IErrorReceiver * errors, IHqlExpressi
 extern HQL_API IHqlExpression * checkCreateConcreteModule(IErrorReceiver * errors, IHqlExpression * expr, const IHqlExpression * locationExpr);
 extern HQL_API IHqlExpression * createLocationAttr(ISourcePath * filename, int lineno, int column, int position);
 
+extern HQL_API IHqlExpression * queryAmbiguousRollupCondition(IHqlExpression * expr, bool strict);
+extern HQL_API IHqlExpression * queryAmbiguousRollupCondition(IHqlExpression * expr, const HqlExprArray & selects);
+extern HQL_API void filterAmbiguousRollupCondition(HqlExprArray & ambiguousSelects, HqlExprArray & selects, IHqlExpression * expr);
+
 #endif

+ 4 - 0
ecl/hqlcpp/hqlcerrors.hpp

@@ -249,6 +249,8 @@
 #define HQLWRN_ComplexHelperClass               4538
 #define HQLWRN_TryAddingIndependent             4539
 #define HQLWRN_GroupedGlobalFew                 4540
+#define HQLWRN_AmbiguousRollupCondition         4541
+#define HQLWRN_AmbiguousRollupNoGroup           4542
 
 //Temporary errors
 #define HQLERR_OrderOnVarlengthStrings          4601
@@ -519,6 +521,8 @@
 #define HQLWRN_GrammarIsAmbiguous_Text          "The PARSE pattern for activity %d is ambiguous.  This may reduce the efficiency of the PARSE."
 #define HQLWRN_ComplexHelperClass_Text          "Activity %d created a complex helper class (%d)"
 #define HQLWRN_GroupedGlobalFew_Text            "Global few expression is grouped"
+#define HQLWRN_AmbiguousRollupCondition_Text    "ROLLUP condition on '%s' is also modified in the transform"
+#define HQLWRN_AmbiguousRollupNoGroup_Text      "ROLLUP condition - no fields are preserved in the transform - not converted to GROUPed ROLLUP"
 
 #define HQLERR_OrderOnVarlengthStrings_Text     "Rank/Ranked not supported on variable length strings"
 #define HQLERR_DistributionNoSequence_Text      "DISTRIBUTION() only supported at the outer level"

+ 1 - 0
ecl/hqlcpp/hqlcpp.cpp

@@ -1698,6 +1698,7 @@ void HqlCppTranslator::cacheOptions()
         DebugOption(options.normalizeSelectorSequence,"normalizeSelectorSequence",false),  // For tracking down why projects are not commoned up
         DebugOption(options.removeXpathFromOutput,"removeXpathFromOutput",false),
         DebugOption(options.canLinkConstantRows,"canLinkConstantRows",true),
+        DebugOption(options.checkAmbiguousRollupCondition,"checkAmbiguousRollupCondition",true),
     };
 
     //get options values from workunit

+ 3 - 0
ecl/hqlcpp/hqlcpp.ipp

@@ -714,6 +714,7 @@ struct HqlCppOptions
     bool                normalizeSelectorSequence;
     bool                removeXpathFromOutput;
     bool                canLinkConstantRows;
+    bool                checkAmbiguousRollupCondition;
 };
 
 //Any information gathered while processing the query should be moved into here, rather than cluttering up the translator class
@@ -1860,6 +1861,8 @@ public:
     void checkNormalized(HqlExprArray & exprs);
     void checkNormalized(BuildCtx & ctx, IHqlExpression * expr);
 
+    void checkAmbiguousRollupCondition(IHqlExpression * expr);
+
 protected:
     HqlCppInstance *    code;
     IHqlScope *         internalScope;

+ 26 - 0
ecl/hqlcpp/hqlhtcpp.cpp

@@ -13709,6 +13709,20 @@ ABoundActivity * HqlCppTranslator::doBuildActivityDistribute(BuildCtx & ctx, IHq
 //---------------------------------------------------------------------------
 // no_rollup
 
+void HqlCppTranslator::checkAmbiguousRollupCondition(IHqlExpression * expr)
+{
+    IHqlExpression * select = queryAmbiguousRollupCondition(expr, false);
+    if (select)
+    {
+        IHqlExpression * dataset = expr->queryChild(0);
+        OwnedHqlExpr newSelect = replaceSelector(select, dataset->queryNormalizedSelector(), queryActiveTableSelector());
+        StringBuffer selectText;
+        getExprECL(newSelect, selectText);
+        reportWarning(queryLocation(expr), ECODETEXT(HQLWRN_AmbiguousRollupCondition), selectText.str());
+    }
+}
+
+
 ABoundActivity * HqlCppTranslator::doBuildActivityRollup(BuildCtx & ctx, IHqlExpression * expr)
 {
     StringBuffer s;
@@ -13733,8 +13747,20 @@ ABoundActivity * HqlCppTranslator::doBuildActivityRollup(BuildCtx & ctx, IHqlExp
 
     buildInstancePrefix(instance);
 
+    if (options.checkAmbiguousRollupCondition)
+        checkAmbiguousRollupCondition(expr);
+
     buildDedupFilterFunction(instance->startctx, equalities, conds, dataset, selSeq);
     buildRollupTransformFunction(instance->startctx, dataset, transform, selSeq);
+
+    OwnedHqlExpr left = createSelector(no_left, dataset, selSeq);
+    OwnedHqlExpr right = createSelector(no_right, dataset, selSeq);
+    StringBuffer flags;
+    if (cond->usesSelector(left) || cond->usesSelector(right))
+        flags.append("|RFrolledismatchleft");
+    if (flags.length())
+        doBuildUnsignedFunction(instance->classctx, "getFlags", flags.str()+1);
+    
     buildInstanceSuffix(instance);
 
     buildConnectInputOutput(ctx, instance, boundDataset, 0, 0);

+ 43 - 16
ecl/hqlcpp/hqlttcpp.cpp

@@ -2207,25 +2207,52 @@ IHqlExpression * ThorHqlTransformer::normalizeRollup(IHqlExpression * expr)
 
         if (equalities.ordinality())
         {
-            OwnedHqlExpr groupOrder = createValueSafe(no_sortlist, makeSortListType(NULL), equalities);
-
-            if (isPartitionedForGroup(dataset, groupOrder, false))
-                return appendOwnedOperand(expr, createLocalAttribute());
+            OwnedHqlExpr left = createSelector(no_left, dataset, querySelSeq(expr));
 
-            HqlExprArray groupArgs, rollupArgs;
-            groupArgs.append(*LINK(dataset));
-            groupArgs.append(*LINK(groupOrder));
+            //If anything in the join condition references LEFT then the whole condition is currently passed the modified row
+            //so remove any fields that are modified in the transform
+            HqlExprArray ambiguousSelects;
+            if (cond->usesSelector(left))
+                filterAmbiguousRollupCondition(ambiguousSelects, equalities, expr);
 
-            OwnedHqlExpr group = createDataset(no_group, groupArgs);
-            group.setown(cloneInheritedAnnotations(expr, group));
-            rollupArgs.append(*LINK(group));
-            if (extra)
-                rollupArgs.append(*extra.getClear());
+            if (equalities.ordinality() == 0)
+            {
+                translator.reportWarning(queryLocation(expr), ECODETEXT(HQLWRN_AmbiguousRollupNoGroup));
+            }
             else
-                rollupArgs.append(*createConstant(true));
-            unwindChildren(rollupArgs, expr, 2);
-            OwnedHqlExpr ungroup = createDataset(no_group, expr->clone(rollupArgs), NULL);
-            return cloneInheritedAnnotations(expr, ungroup);
+            {
+                OwnedHqlExpr groupOrder = createValueSafe(no_sortlist, makeSortListType(NULL), equalities);
+
+                if (isPartitionedForGroup(dataset, groupOrder, false))
+                    return appendOwnedOperand(expr, createLocalAttribute());
+
+                //This list can only contain items if the filter is using left/right => expand to an equality
+                IHqlExpression * selector = dataset->queryNormalizedSelector();
+                OwnedHqlExpr right = createSelector(no_right, dataset, querySelSeq(expr));
+                ForEachItemIn(i, ambiguousSelects)
+                {
+                    IHqlExpression * select = &ambiguousSelects.item(i);
+                    OwnedHqlExpr leftSelect = replaceSelector(select, selector, left);
+                    OwnedHqlExpr rightSelect = replaceSelector(select, selector, right);
+                    IHqlExpression * eq = createBoolExpr(no_eq, leftSelect.getClear(), rightSelect.getClear());
+                    extendConditionOwn(extra, no_and, eq);
+                }
+
+                HqlExprArray groupArgs, rollupArgs;
+                groupArgs.append(*LINK(dataset));
+                groupArgs.append(*LINK(groupOrder));
+
+                OwnedHqlExpr group = createDataset(no_group, groupArgs);
+                group.setown(cloneInheritedAnnotations(expr, group));
+                rollupArgs.append(*LINK(group));
+                if (extra)
+                    rollupArgs.append(*extra.getClear());
+                else
+                    rollupArgs.append(*createConstant(true));
+                unwindChildren(rollupArgs, expr, 2);
+                OwnedHqlExpr ungroup = createDataset(no_group, expr->clone(rollupArgs), NULL);
+                return cloneInheritedAnnotations(expr, ungroup);
+            }
         }
     }
 

+ 4 - 1
ecl/hthor/hthor.cpp

@@ -2287,7 +2287,10 @@ const void *CHThorRollupActivity::nextInGroup()
             {
                 left.setown(rowBuilder.finalizeRowClear(outSize));
             }
-            prev.set(left);
+            if (helper.getFlags() & RFrolledismatchleft)
+                prev.set(left);
+            else
+                prev.set(right);
         }
         catch(IException * e)
         {

+ 5 - 1
roxie/ccd/ccdserver.cpp

@@ -6746,7 +6746,11 @@ public:
                 unsigned outSize = helper.transform(rowBuilder, left, right);
                 if (outSize)
                     left.setown(rowBuilder.finalizeRowClear(outSize));
-                prev.set(left);
+
+                if (helper.getFlags() & RFrolledismatchleft)
+                    prev.set(left);
+                else
+                    prev.set(right);
             }
             catch(IException * E)
             {

+ 5 - 0
rtl/include/eclhelper.hpp

@@ -1352,8 +1352,13 @@ struct IHThorLinkedRawIteratorArg : public IHThorArg
 };
 
 
+enum {
+    RFrolledismatchleft = 0x00001,      // Is the value of left passed to matches() the result of the rollup?
+};
+
 struct IHThorRollupArg : public IHThorArg
 {
+    virtual unsigned getFlags() = 0;
     virtual bool matches(const void * _left, const void * _right) = 0;
     virtual size32_t transform(ARowBuilder & rowBuilder, const void * _left, const void * _right) = 0;
 };

+ 1 - 0
rtl/include/eclhelper_base.hpp

@@ -1112,6 +1112,7 @@ class CThorRollupArg : public CThorArg, implements IHThorRollupArg
         return NULL;
     }
 
+    virtual unsigned getFlags() { return 0; }
     virtual bool matches(const void * _left, const void * _right) { return true; }
 };
 

+ 41 - 0
testing/ecl/key/rollup4.xml

@@ -0,0 +1,41 @@
+<Dataset name='Result 1'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><value1>3</value1><value2>8</value2></Row>
+ <Row><value1>3</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><value1>2</value1><value2>3</value2></Row>
+ <Row><value1>1</value1><value2>5</value2></Row>
+ <Row><value1>3</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><value1>1</value1><value2>1</value2></Row>
+ <Row><value1>2</value1><value2>1</value2></Row>
+</Dataset>
+<Dataset name='Result 8'>
+ <Row><value1>1</value1><value2>1</value2></Row>
+ <Row><value1>2</value1><value2>1</value2></Row>
+</Dataset>
+<Dataset name='Result 9'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>
+<Dataset name='Result 10'>
+ <Row><value1>1</value1><value2>8</value2></Row>
+ <Row><value1>2</value1><value2>4</value2></Row>
+</Dataset>

+ 62 - 0
testing/ecl/rollup4.ecl

@@ -0,0 +1,62 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+MyRec := RECORD
+    UNSIGNED Value1;
+    UNSIGNED value2;
+END;
+
+SomeFile := DATASET([{1,1},
+                     {1,2},
+                     {1,5},
+                     {2,1},
+                     {2,3}],MyRec);
+
+MyRec t1(myRec l, myRec r) := TRANSFORM
+    SELF.value1 := l.value1;
+    SELF.value2 := l.value2 + r.value2;
+END;
+                     
+MyRec t2(myRec l, myRec r) := TRANSFORM
+    SELF.value1 := l.value1+1;
+    SELF.value2 := l.value2 + r.value2;
+END;
+
+MyRec t3(myRec l, myRec r) := TRANSFORM
+    SELF.value1 := r.value1;
+    SELF.value2 := l.value2 + r.value2;
+END;
+                     
+MyRec t4(myRec l, myRec r) := TRANSFORM
+    SELF := IF(l.value1 < r.value2, l, r);
+END;
+                     
+MyRec t5(myRec l, myRec r) := TRANSFORM
+    SELF.value1 := IF(l.value1 < r.value2, r.value1, l.value1);
+    SELF.value2 := l.value2 + r.value2;
+END;
+                     
+OUTPUT(ROLLUP(SomeFile, value1, t1(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, LEFT.value1 = RIGHT.value1, t1(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, value1, t2(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, LEFT.value1 = RIGHT.value1, t2(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, value1, t3(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, LEFT.value1 = RIGHT.value1, t3(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, value1, t4(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, LEFT.value1 = RIGHT.value1, t4(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, value1, t5(LEFT,RIGHT)));
+OUTPUT(ROLLUP(SomeFile, LEFT.value1 = RIGHT.value1, t5(LEFT,RIGHT)));

+ 5 - 1
thorlcr/activities/rollup/throllupslave.cpp

@@ -530,7 +530,11 @@ public:
             unsigned thisSize = ruhelper->transform(ret, keptTransformed, next);
             if (thisSize)
                 keptTransformed.setown(ret.finalizeRowClear(thisSize));
-            kept.set(keptTransformed);
+
+            if (ruhelper->getFlags() & RFrolledismatchleft)
+                kept.set(keptTransformed);
+            else
+                kept.setown(next.getClear());
         }
         OwnedConstThorRow row = keptTransformed.getClear();
         kept.setown(next.getClear());