Browse Source

Merge pull request #11938 from ghalliday/issue21041

 HPCC-21041 Reimplement the code to detect ambiguous selectors

Reviewed-By: Shamser Ahmed <shamser.ahmed@lexisnexis.co.uk>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 6 years ago
parent
commit
829a7919b4

+ 27 - 142
ecl/hql/hqlexpr.cpp

@@ -5321,14 +5321,6 @@ inline void addUniqueTable(HqlExprCopyArray & array, IHqlExpression * ds)
         array.append(*ds);
 }
 
-inline void addHiddenTable(HqlExprCopyArray & array, IHqlExpression * ds)
-{
-#if defined(GATHER_HIDDEN_SELECTORS)
-    if (array.find(*ds) == NotFound)
-        array.append(*ds);
-#endif
-}
-
 inline void addActiveTable(HqlExprCopyArray & array, IHqlExpression * ds)
 {
     //Sometimes the "dataset" passed in happens to be a no_select of a row field from a dataset.
@@ -5366,29 +5358,18 @@ inline void addActiveTable(UsedExpressionHashTable & array, IHqlExpression * ds)
 CUsedTables::CUsedTables()
 {
     tables.single = NULL;
-    numTables = 0;
     numActiveTables = 0;
 }
 
 CUsedTables::~CUsedTables()
 {
-    if (numTables == 1)
-    {
-        if (numActiveTables == 0)
-            tables.single->Release();
-    }
-    else if (numTables != 0)
-    {
-        for (unsigned i=numActiveTables; i < numTables; i++)
-            tables.multi[i]->Release();
+    if (numActiveTables > 1)
         delete [] tables.multi;
-    }
 }
 
-
 bool CUsedTables::usesSelector(IHqlExpression * selector) const
 {
-    if (numTables > 1)
+    if (numActiveTables > 1)
     {
         for (unsigned i=0; i < numActiveTables; i++)
         {
@@ -5404,83 +5385,47 @@ bool CUsedTables::usesSelector(IHqlExpression * selector) const
 
 void CUsedTables::gatherTablesUsed(CUsedTablesBuilder & used) const
 {
-    if (numTables == 0)
+    if (numActiveTables == 0)
         return;
-    if (numTables == 1)
+    if (numActiveTables == 1)
     {
-        if (numActiveTables == 1)
-            used.addActiveTable(tables.single);
-        else
-            used.addNewTable(tables.single);
+        used.addActiveTable(tables.single);
     }
     else
     {
         for (unsigned i1=0; i1 < numActiveTables; i1++)
             used.addActiveTable(tables.multi[i1]);
-        for (unsigned i2=numActiveTables; i2 < numTables; i2++)
-            used.addNewTable(tables.multi[i2]);
     }
 }
 
-void CUsedTables::gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) const
+void CUsedTables::gatherTablesUsed(HqlExprCopyArray & inScope) const
 {
-    if (numTables == 0)
+    if (numActiveTables == 0)
         return;
-    if (numTables == 1)
+    if (numActiveTables == 1)
     {
-        if (numActiveTables == 1)
-        {
-            if (inScope)
-                addUniqueTable(*inScope, tables.single);
-        }
-        else
-        {
-            if (newScope)
-                addUniqueTable(*newScope, tables.single);
-        }
+        addUniqueTable(inScope, tables.single);
     }
     else
     {
-        if (inScope)
-        {
-            for (unsigned i1=0; i1 < numActiveTables; i1++)
-                addUniqueTable(*inScope, tables.multi[i1]);
-        }
-        if (newScope)
-        {
-            for (unsigned i2=numActiveTables; i2 < numTables; i2++)
-                addUniqueTable(*newScope, tables.multi[i2]);
-        }
+        for (unsigned i1=0; i1 < numActiveTables; i1++)
+            addUniqueTable(inScope, tables.multi[i1]);
     }
 }
 
 
-void CUsedTables::set(HqlExprCopyArray & activeTables, HqlExprCopyArray & newTables)
+void CUsedTables::set(HqlExprCopyArray & activeTables)
 {
     numActiveTables = activeTables.ordinality();
-    numTables = numActiveTables + newTables.ordinality();
-    if (numTables == 1)
+    if (numActiveTables == 1)
     {
-        if (numActiveTables == 1)
-        {
-            tables.single = &activeTables.item(0);
-        }
-        else
-        {
-            tables.single = &newTables.item(0);
-            tables.single->Link();
-        }
+        tables.single = &activeTables.item(0);
     }
-    else if (numTables != 0)
+    else if (numActiveTables != 0)
     {
-        IHqlExpression * * multi = new IHqlExpression * [numTables];
+        IHqlExpression * * multi = new IHqlExpression * [numActiveTables];
         for (unsigned i1=0; i1 < numActiveTables; i1++)
             multi[i1] = &activeTables.item(i1);
-        for (unsigned i2=numActiveTables; i2 < numTables; i2++)
-        {
-            multi[i2] = &newTables.item(i2-numActiveTables);
-            multi[i2]->Link();
-        }
         tables.multi = multi;
     }
 }
@@ -5488,29 +5433,11 @@ void CUsedTables::set(HqlExprCopyArray & activeTables, HqlExprCopyArray & newTab
 void CUsedTables::setActiveTable(IHqlExpression * expr)
 {
     tables.single = expr;
-    numTables = 1;
     numActiveTables = 1;
 }
 
 //---------------------------------------------------------------------------------------------------------------------
 
-void CUsedTablesBuilder::addNewTable(IHqlExpression * expr)
-{
-    addUniqueTable(newScopeTables, expr);
-}
-
-void CUsedTablesBuilder::addHiddenSelector(IHqlExpression * expr)
-{
-#if defined(GATHER_HIDDEN_SELECTORS)
-    //expr is always a newly created selector.  If this expression isn't shared, then it will not be used anywhere else
-    //in the expression tree, so don't add it.
-    if (!static_cast<CHqlExpression *>(expr)->IsShared())
-        return;
-
-    addUniqueTable(newScopeTables, expr);
-#endif
-}
-
 void CUsedTablesBuilder::addActiveTable(IHqlExpression * expr)
 {
     ::addActiveTable(inScopeTables, expr);
@@ -5599,10 +5526,8 @@ inline void expand(HqlExprCopyArray & target, const UsedExpressionHashTable & so
 void CUsedTablesBuilder::set(CUsedTables & tables)
 {
     HqlExprCopyArray inTables;
-    HqlExprCopyArray newTables;
     expand(inTables, inScopeTables);
-    expand(newTables, newScopeTables);
-    tables.set(inTables, newTables);
+    tables.set(inTables);
 }
 
 //---------------------------------------------------------------------------------------------------------------------
@@ -5617,9 +5542,6 @@ void CHqlExpressionWithTables::cacheChildrenTablesUsed(CUsedTablesBuilder & used
 
 void CHqlExpressionWithTables::cacheInheritChildTablesUsed(IHqlExpression * ds, CUsedTablesBuilder & used, const HqlExprCopyArray & childInScopeTables)
 {
-    //The argument to the operator is a new table, don't inherit grandchildren
-    used.addNewTable(ds);
-
     //Any datasets in that are referenced by the child are included, but not including
     //the dataset itself.
     IHqlExpression * normalizedDs = ds->queryNormalizedSelector();
@@ -5633,20 +5555,7 @@ void CHqlExpressionWithTables::cacheInheritChildTablesUsed(IHqlExpression * ds,
 
 void CHqlExpressionWithTables::cacheTableUseage(CUsedTablesBuilder & used, IHqlExpression * expr)
 {
-#ifdef GATHER_HIDDEN_SELECTORS
     expr->gatherTablesUsed(used);
-#else
-    if (expr->getOperator() == no_activerow)
-        used.addActiveTable(expr->queryChild(0));
-    else
-    {
-        HqlExprCopyArray childInScopeTables;
-        expr->gatherTablesUsed(NULL, &childInScopeTables);
-
-        //The argument to the operator is a new table, don't inherit grandchildren
-        cacheInheritChildTablesUsed(expr, used, childInScopeTables);
-    }
-#endif
 }
 
 void CHqlExpressionWithTables::cachePotentialTablesUsed(CUsedTablesBuilder & used)
@@ -5740,8 +5649,6 @@ void CHqlExpressionWithTables::cacheTablesProcessChildScope(CUsedTablesBuilder &
             }
             if (!ignoreInputs)
                 cacheChildrenTablesUsed(used, 0, 1);
-
-            used.addHiddenSelector(left);
             break;
         }
     case childdataset_left:
@@ -5755,8 +5662,6 @@ void CHqlExpressionWithTables::cacheTablesProcessChildScope(CUsedTablesBuilder &
 
             if (!ignoreInputs)
                 cacheChildrenTablesUsed(used, 0, 1);
-
-            used.addHiddenSelector(left);
             break;
         }
     case childdataset_same_left_right:
@@ -5774,9 +5679,6 @@ void CHqlExpressionWithTables::cacheTablesProcessChildScope(CUsedTablesBuilder &
 
             if (!ignoreInputs)
                 cacheChildrenTablesUsed(used, 0, 1);
-
-            used.addHiddenSelector(left);
-            used.addHiddenSelector(right);
             break;
         }
     case childdataset_top_left_right:
@@ -5792,9 +5694,6 @@ void CHqlExpressionWithTables::cacheTablesProcessChildScope(CUsedTablesBuilder &
             used.removeActive(right);
             used.removeRows(this, left, right);
             cacheChildrenTablesUsed(used, 0, 1);
-
-            used.addHiddenSelector(left);
-            used.addHiddenSelector(right);
             break;
         }
     case childdataset_leftright: 
@@ -5822,9 +5721,6 @@ void CHqlExpressionWithTables::cacheTablesProcessChildScope(CUsedTablesBuilder &
                 if (!ignoreInputs)
                     cacheChildrenTablesUsed(used, 0, 2);
             }
-
-            used.addHiddenSelector(left);
-            used.addHiddenSelector(right);
             break;
         }
         break;
@@ -5885,18 +5781,8 @@ void CHqlExpressionWithTables::calcTablesUsed(CUsedTablesBuilder & used, bool ig
     case NO_AGGREGATE:
     case no_createset:
         {
-#ifdef GATHER_HIDDEN_SELECTORS
             cachePotentialTablesUsed(used);
             used.removeParent(queryChild(0));
-#else
-            HqlExprCopyArray childInScopeTables;
-            ForEachChild(idx, this)
-                queryChild(idx)->gatherTablesUsed(NULL, &childInScopeTables);
-
-            //The argument to the operator is a new table, don't inherit grandchildren
-            IHqlExpression * ds = queryChild(0);
-            cacheInheritChildTablesUsed(ds, used, childInScopeTables);
-#endif
         }
         break;
     case no_sizeof:
@@ -6099,10 +5985,10 @@ bool CHqlExpressionWithTables::usesSelector(IHqlExpression * selector)
     return usedTables.usesSelector(selector);
 }
 
-void CHqlExpressionWithTables::gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope)
+void CHqlExpressionWithTables::gatherTablesUsed(HqlExprCopyArray & inScope)
 {
     cacheTablesUsed();
-    usedTables.gatherTablesUsed(newScope, inScope);
+    usedTables.gatherTablesUsed(inScope);
 }
 
 void CHqlExpressionWithTables::gatherTablesUsed(CUsedTablesBuilder & used)
@@ -6251,17 +6137,16 @@ void CHqlSelectBaseExpression::gatherTablesUsed(CUsedTablesBuilder & used)
     }
 }
 
-void CHqlSelectBaseExpression::gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope)
+void CHqlSelectBaseExpression::gatherTablesUsed(HqlExprCopyArray & inScope)
 {
     IHqlExpression * ds = queryChild(0);
     if (isSelectRootAndActive())
     {
-        if (inScope)
-            ::addActiveTable(*inScope, ds);
+        ::addActiveTable(inScope, ds);
     }
     else
     {
-        ds->gatherTablesUsed(newScope, inScope);
+        ds->gatherTablesUsed(inScope);
     }
 }
 
@@ -7289,9 +7174,9 @@ void CHqlAnnotation::gatherTablesUsed(CUsedTablesBuilder & used)
     body->gatherTablesUsed(used);
 }
 
-void CHqlAnnotation::gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope)
+void CHqlAnnotation::gatherTablesUsed(HqlExprCopyArray & inScope)
 {
-    body->gatherTablesUsed(newScope, inScope);
+    body->gatherTablesUsed(inScope);
 }
 
 IHqlExpression *CHqlAnnotation::queryChild(unsigned idx) const
@@ -13501,7 +13386,7 @@ bool canEvaluateInScope(const HqlExprCopyArray & activeScopes, const HqlExprCopy
 bool canEvaluateInScope(const HqlExprCopyArray & activeScopes, IHqlExpression * expr)
 {
     HqlExprCopyArray scopesUsed;
-    expr->gatherTablesUsed(NULL, &scopesUsed);
+    expr->gatherTablesUsed(scopesUsed);
     return canEvaluateInScope(activeScopes, scopesUsed);
 }
 
@@ -13511,11 +13396,11 @@ bool exprReferencesDataset(IHqlExpression * expr, IHqlExpression * dataset)
     return expr->usesSelector(dataset->queryNormalizedSelector());
 }
 
-void gatherChildTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope, IHqlExpression * expr, unsigned firstChild)
+void gatherChildTablesUsed(HqlExprCopyArray & inScope, IHqlExpression * expr, unsigned firstChild)
 {
     unsigned max = expr->numChildren();
     for (unsigned i=firstChild; i < max; i++)
-        expr->queryChild(i)->gatherTablesUsed(newScope, inScope);
+        expr->queryChild(i)->gatherTablesUsed(inScope);
 }
 
 extern IHqlScope *createService()

+ 2 - 4
ecl/hql/hqlexpr.hpp

@@ -34,8 +34,6 @@
 //There may also problems with queryRecord() which needs to really be replaced with recordof(x), especially if "templates" are delayed expanded.
 //To work properly it may require many of the transformations in hqlgram2.cpp to be moved to after the expansion.  (E.g., BUILD)
 
-#define GATHER_HIDDEN_SELECTORS
-
 #define XPATH_CONTENTS_TEXT     "<>"
 #define INTERNAL_LOCAL_MODULE_NAME "_local_directory_"
 
@@ -1184,7 +1182,7 @@ interface IHqlExpression : public IInterface
     virtual bool isIndependentOfScope() = 0;
     virtual bool usesSelector(IHqlExpression * selector) = 0;
     virtual bool isIndependentOfScopeIgnoringInputs() = 0;
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) = 0;
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) = 0;
     virtual IValue *queryValue() const = 0;
     virtual IInterface *queryUnknownExtra() = 0;
     virtual unsigned __int64 querySequenceExtra() = 0;              // sequence, but also overloaded with parameter number
@@ -1705,7 +1703,7 @@ extern HQL_API bool filterIsUnkeyed(IHqlExpression * expr);
 extern HQL_API bool canEvaluateGlobally(IHqlExpression * expr);
 extern HQL_API bool isTrivialDataset(IHqlExpression * expr);
 extern HQL_API bool isInlineTrivialDataset(IHqlExpression * expr);
-extern HQL_API void gatherChildTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope, IHqlExpression * expr, unsigned firstChild);
+extern HQL_API void gatherChildTablesUsed(HqlExprCopyArray & inScope, IHqlExpression * expr, unsigned firstChild);
 extern HQL_API IHqlScope * closeScope(IHqlScope * scope);
 extern HQL_API IIdAtom * queryPatternName(IHqlExpression * expr);
 extern HQL_API IHqlExpression * closeAndLink(IHqlExpression * expr);

+ 8 - 11
ecl/hql/hqlexpr.ipp

@@ -85,8 +85,8 @@ public:
     inline bool isIndependentOfScope() const { return (numActiveTables == 0); }
     bool usesSelector(IHqlExpression * selector) const;
     void gatherTablesUsed(CUsedTablesBuilder & used) const;
-    void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) const;
-    void set(HqlExprCopyArray & _activeTables, HqlExprCopyArray & _newTables);
+    void gatherTablesUsed(HqlExprCopyArray & inScope) const;
+    void set(HqlExprCopyArray & _activeTables);
     void setActiveTable(IHqlExpression * expr);
 
 private:
@@ -95,7 +95,6 @@ private:
         IHqlExpression * single;
         IHqlExpression * * multi;
     } tables;
-    unsigned numTables;
     unsigned numActiveTables;
 };
 
@@ -131,8 +130,6 @@ protected:
 class HQL_API CUsedTablesBuilder
 {
 public:
-    void addNewTable(IHqlExpression * expr);
-    void addHiddenSelector(IHqlExpression * expr);
     void addActiveTable(IHqlExpression * expr);
     void cleanupProduction();
     inline void removeActive(IHqlExpression * expr) { inScopeTables.remove(expr); }
@@ -145,7 +142,6 @@ public:
 
 protected:
     UsedExpressionHashTable inScopeTables;     // may need to rename, since use has changed.
-    UsedExpressionHashTable newScopeTables;
 };
 
 #ifdef HQLEXPR_MULTI_THREADED
@@ -352,7 +348,7 @@ public:
     virtual bool isIndependentOfScopeIgnoringInputs() override;
     virtual bool usesSelector(IHqlExpression * selector) override;
     virtual void gatherTablesUsed(CUsedTablesBuilder & used) override;
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) override;
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) override;
 
 protected:
     void cacheChildrenTablesUsed(CUsedTablesBuilder & used, unsigned from, unsigned to);
@@ -366,6 +362,7 @@ protected:
 
 protected:
     CUsedTables usedTables;
+    //unsigned spare = 0; // in 64bit this space is currently wasted
 };
 
 class HQL_API CHqlExpressionWithType : public CHqlExpressionWithTables
@@ -425,7 +422,7 @@ public:
     virtual bool isIndependentOfScopeIgnoringInputs() override;
     virtual bool usesSelector(IHqlExpression * selector) override;
     virtual void gatherTablesUsed(CUsedTablesBuilder & used) override;
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) override;
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) override;
 
     virtual void calcNormalized() = 0;
 
@@ -614,7 +611,7 @@ public:
     virtual bool isIndependentOfScopeIgnoringInputs() override;
     virtual bool usesSelector(IHqlExpression * selector) override;
     virtual void gatherTablesUsed(CUsedTablesBuilder & used) override;
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) override;
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) override;
     virtual IValue *queryValue() const override;
     virtual IInterface *queryUnknownExtra() override;
     virtual unsigned __int64 querySequenceExtra() override;
@@ -1424,7 +1421,7 @@ public:
     virtual bool isIndependentOfScopeIgnoringInputs() override { return true; }
     virtual bool usesSelector(IHqlExpression * selector) override { return false; }
     virtual void gatherTablesUsed(CUsedTablesBuilder & used) override {}
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) override {}
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) override {}
 };
 
 class CHqlParameter : public CHqlExpressionWithType
@@ -1620,7 +1617,7 @@ public:
     virtual bool isIndependentOfScopeIgnoringInputs() override { return true; }
     virtual bool usesSelector(IHqlExpression * selector) override { return false; }
     virtual void gatherTablesUsed(CUsedTablesBuilder & used) override {}
-    virtual void gatherTablesUsed(HqlExprCopyArray * newScope, HqlExprCopyArray * inScope) override {}
+    virtual void gatherTablesUsed(HqlExprCopyArray & inScope) override {}
 };
 
 class CHqlAttribute : public CHqlExpressionWithTables

+ 2 - 2
ecl/hql/hqlfilter.cpp

@@ -664,7 +664,7 @@ bool FilterExtractor::isIndexInvariant(IHqlExpression * expr, bool includeRoot)
         return false;
 
     HqlExprCopyArray scopeUsed;
-    expr->gatherTablesUsed(NULL, &scopeUsed);
+    expr->gatherTablesUsed(scopeUsed);
 
     IHqlExpression * search = tableExpr->queryNormalizedSelector();
     ForEachItemIn(i, scopeUsed)
@@ -1396,7 +1396,7 @@ bool FilterExtractor::extractIfFilter(KeyConditionInfo & matches, IHqlExpression
 bool FilterExtractor::containsTableSelects(IHqlExpression * expr)
 {
     HqlExprCopyArray inScope;
-    expr->gatherTablesUsed(NULL, &inScope);
+    expr->gatherTablesUsed(inScope);
 
     //Check that cursors for all inScope tables are already bound in the start context
     return inScope.find(*tableExpr->queryNormalizedSelector()) != NotFound;

+ 2 - 2
ecl/hql/hqlopt.cpp

@@ -445,7 +445,7 @@ IHqlExpression * CTreeOptimizer::moveFilterOverSelect(IHqlExpression * expr)
     {
         IHqlExpression & cur = args.item(i);
         inScope.kill();
-        cur.gatherTablesUsed(NULL, &inScope);
+        cur.gatherTablesUsed(inScope);
         if (inScope.find(*newScope) == NotFound)
             hoisted.append(OLINK(cur));
         else
@@ -1455,7 +1455,7 @@ IHqlExpression * splitJoinFilter(IHqlExpression * expr, HqlExprArray * leftOnly,
     }
 
     HqlExprCopyArray scopeUsed;
-    expr->gatherTablesUsed(NULL, &scopeUsed);
+    expr->gatherTablesUsed(scopeUsed);
     if (scopeUsed.ordinality() == 1)
     {
         node_operator scopeOp = scopeUsed.item(0).getOperator();

+ 1 - 1
ecl/hql/hqlscope.cpp

@@ -200,7 +200,7 @@ extern HQL_API void checkIndependentOfScope(IHqlExpression * expr)
         return;
 
     HqlExprCopyArray scopeUsed;
-    expr->gatherTablesUsed(NULL, &scopeUsed);
+    expr->gatherTablesUsed(scopeUsed);
 
     HqlExprArray exprs;
     exprs.append(*LINK(expr));

+ 5 - 2
ecl/hql/hqltrans.cpp

@@ -1309,12 +1309,15 @@ void NewHqlTransformer::analyseExpr(IHqlExpression * expr)
     case no_record:
         break;
     case no_attr:
-    case no_attr_expr:
     case no_attr_link:
     case no_constant:
     case no_translated:
     case no_getresult:
         break;
+    case no_attr_expr:
+        if (analyseAttributes())
+            analyseChildren(expr);
+        break;
     case no_newkeyindex:
         //by default only look at the filename
         analyseExpr(expr->queryChild(3));
@@ -1990,7 +1993,7 @@ void NewHqlTransformer::setMapping(IHqlExpression * oldValue, IHqlExpression * n
 bool NewHqlTransformer::needToUpdateSelectors(IHqlExpression * expr)
 {
     HqlExprCopyArray scopesUsed;
-    expr->gatherTablesUsed(nullptr, &scopesUsed);
+    expr->gatherTablesUsed(scopesUsed);
     ForEachItemIn(i, scopesUsed)
     {
         IHqlExpression * cur = &scopesUsed.item(i);

+ 3 - 0
ecl/hql/hqltrans.ipp

@@ -433,6 +433,7 @@ class HQL_API NewHqlTransformer : public ANewHqlTransformer
 public:
     enum { TFunconditional = 1, TFconditional = 2, TFsequential = 4 };
     enum { TCOtransformNonActive        = 0x0001,
+           TCOanalyseAttributes         = 0x0002,
          };
 
     NewHqlTransformer(HqlTransformerInfo & _info);
@@ -497,6 +498,8 @@ protected:
     void setMapping(IHqlExpression * oldValue, IHqlExpression * newValue);
     void setSelectorMapping(IHqlExpression * oldValue, IHqlExpression * newValue);
 
+    inline bool analyseAttributes() const { return (optimizeFlags & TCOanalyseAttributes) != 0; }
+
 protected:
     void setMappingOnly(IHqlExpression * oldValue, IHqlExpression * newValue);
     /*

+ 1 - 1
ecl/hqlcpp/hqlcpp.cpp

@@ -4122,7 +4122,7 @@ bool HqlCppTranslator::canEvaluateInContext(BuildCtx & ctx, IHqlExpression * exp
 bool mustEvaluateInContext(BuildCtx & ctx, IHqlExpression * expr)
 {
     HqlExprCopyArray required;
-    expr->gatherTablesUsed(NULL, &required);
+    expr->gatherTablesUsed(required);
     if (required.ordinality() == 0)
         return false;
 

+ 1 - 1
ecl/hqlcpp/hqlgraph.cpp

@@ -456,7 +456,7 @@ void LogicalGraphCreator::endSubGraph(bool nested)
 static bool exprIsGlobal(IHqlExpression * expr)
 {
     HqlExprCopyArray inScope;
-    expr->gatherTablesUsed(NULL, &inScope);
+    expr->gatherTablesUsed(inScope);
     return inScope.ordinality() == 0;
 }
 

+ 197 - 107
ecl/hqlcpp/hqlresource.cpp

@@ -350,9 +350,21 @@ protected:
     bool alwaysSingle;
 };
 
+class EclChildSplitPointInfo : public NewTransformInfo
+{
+public:
+    EclChildSplitPointInfo(IHqlExpression * _expr) : NewTransformInfo(_expr) { }
+
+public:
+    unsigned visitedMask = 0;
+    bool neverHoist = false;
+};
+
 
 class EclChildSplitPointLocator : public EclHoistLocator
 {
+    typedef unsigned MaskType;
+
 public:
     EclChildSplitPointLocator(IHqlExpression * _original, HqlExprCopyArray & _selectors, ChildDependentArray & _matches, bool _groupedChildIterators)
     : EclHoistLocator(_matches), selectors(_selectors), groupedChildIterators(_groupedChildIterators)
@@ -370,22 +382,51 @@ public:
             okToSelect = true;
             break;
         }
+        optimizeFlags |= TCOanalyseAttributes;
     }
 
-    void findSplitPoints(IHqlExpression * expr, unsigned from, unsigned to, bool _alwaysSingle, bool _executedOnce)
+    void findSplitPoints(IHqlExpression * expr, unsigned from, unsigned to, bool _alwaysSingle, bool _executedOnce, unsigned pass)
     {
         alwaysSingle = _alwaysSingle;
         for (unsigned i=from; i < to; i++)
         {
             IHqlExpression * cur = expr->queryChild(i);
             executedOnce = _executedOnce || cur->isAttribute();     // assume attributes are only executed once.
-            findSplitPoints(cur);
+            findSplitPoints(cur, pass);
         }
         alwaysSingle = false;
     }
 
 protected:
-    void findSplitPoints(IHqlExpression * expr)
+    bool isPossibleToHoist(IHqlExpression * expr)
+    {
+        if (selectors.empty())
+            return expr->isIndependentOfScope();
+
+        //Check datasets are available
+        HqlExprCopyArray scopeUsed;
+        expr->gatherTablesUsed(scopeUsed);
+        ForEachItemIn(i, scopeUsed)
+        {
+            IHqlExpression & cur = scopeUsed.item(i);
+            unsigned match = selectors.find(cur);
+            if (match == NotFound)
+                return false;
+
+            if (isAmbiguous(match))
+                return false;
+        }
+
+        return true;
+    }
+    inline bool isAmbiguous(unsigned whichSelector) const
+    {
+        if (tooManySelectors())
+            return (ambiguousMask != 0);
+        return ((MaskType(1) << whichSelector) & ambiguousMask) != 0;
+    }
+
+    void findSplitPoints(IHqlExpression * expr, unsigned pass)
     {
         //containsNonActiveDataset() would be nice - but that isn't percolated outside assigns etc.
         if (containsAnyDataset(expr) || containsMustHoist(expr) || !expr->isIndependentOfScope())
@@ -395,7 +436,7 @@ protected:
                 gatherAmbiguousSelectors(original);
                 gathered = true;
             }
-            analyse(expr, 0);
+            analyse(expr, pass);
         }
     }
 
@@ -419,9 +460,101 @@ protected:
         return alwaysHoist;
     }
 
+    //Caluclate which of the active parent selectors the are in scope for the current expression
+    inline void calculateHidden(MaskType & childMask, unsigned & minHidden, IHqlExpression * expr)
+    {
+        childMask = 0;
+        minHidden = 0;
+        ForEachItemIn(i, selectors)
+        {
+            //Does the expression introduce this selector, and if so, which arguments does it affect
+            unsigned numNonHidden = activityHidesSelectorGetNumNonHidden(expr, &selectors.item(i));
+            if (numNonHidden)
+            {
+                //Add a bit to the mask to indicate this selector is ambiguous
+                childMask |= (MaskType)(1) << i;
+                if ((minHidden == 0) || (numNonHidden < minHidden))
+                    minHidden = numNonHidden;
+            }
+        }
+
+        //If there are too many selectors to maintain a bitset, then set all the bits.
+        if (minHidden && tooManySelectors())
+            childMask = (unsigned)-1;
+    }
+
+    //Check if the expression could be evaluated outside of the current activity
+    void analyseScopeDependence(IHqlExpression * expr)
+    {
+        IHqlExpression * body = expr->queryBody();
+        EclChildSplitPointInfo * extra = queryExtra(body);
+
+        //This is an optimization - should work equally well using the false branch - test it!
+        if (selectors.empty())
+        {
+            if (alreadyVisited(extra))
+                return;
+
+            //Not a child query, so can only hoist if the expression is independent of the scope
+            if (!expr->isIndependentOfScope())
+                extra->neverHoist = true;
+            return EclHoistLocator::analyseExpr(expr);
+        }
+        else
+        {
+            if (alreadyVisited(extra))
+            {
+                //If we have already visited this expression we need to check again if there are selectors that ar
+                //ambiguous that were not the last time we visited it.
+                //Check if there are no bits in the current ambiguous mask not set in the previous mask
+                if ((ambiguousMask & ~extra->visitedMask) == 0)
+                    return;
+                extra->visitedMask |= ambiguousMask;
+            }
+
+            //Check if the expression is dependent on ambiguous selectors, or expressions that are not available in the parent
+            if (!isPossibleToHoist(expr))
+                extra->neverHoist = true;
+
+            switch (getChildDatasetType(body))
+            {
+            case childdataset_none:
+            case childdataset_many_noscope:
+            case childdataset_many:
+            case childdataset_map:
+            case childdataset_dataset_noscope:
+            case childdataset_if:
+            case childdataset_case:
+            case childdataset_dataset:
+            case childdataset_evaluate:
+                return EclHoistLocator::analyseExpr(expr);
+            }
+
+            unsigned minHidden = 0;
+            MaskType childMask = 0;
+            calculateHidden(childMask, minHidden, expr);
+
+            for (unsigned iDs=0; iDs < minHidden; iDs++)
+                analyseExpr(expr->queryChild(iDs));
+
+            unsigned max = expr->numChildren();
+            MaskType savedMask = ambiguousMask;
+            ambiguousMask |= childMask;
+            for (unsigned iArg=minHidden; iArg < max; iArg++)
+                analyseExpr(expr->queryChild(iArg));
+            ambiguousMask = savedMask;
+        }
+    }
 
     virtual void analyseExpr(IHqlExpression * expr)
     {
+        if (pass == 0)
+        {
+            analyseScopeDependence(expr);
+            return;
+        }
+
+        IHqlExpression * body = expr->queryBody();
         if (alreadyVisited(expr))
             return;
 
@@ -633,71 +766,25 @@ protected:
 
     void gatherAmbiguousSelectors(IHqlExpression * expr)
     {
-        //Horrible code to try and cope with ambiguous left selectors.
-        //o Tree is ambiguous so same child expression can occur in different contexts - so can't depend on the context it is found in to work out if can hoist
-        //o If any selector that is hidden within child expressions matches one in scope then can't hoist it.
-        //If the current expression creates a selector, then can't hoist anything that depends on it [only add to hidden if in selectors to reduce searching]
-        //o Want to hoist as much as possible.
+        //If this is a child query, then some of the selectors that are brought into scope by this activity, or a child activity may
+        //clash with selectors that are brought into scope for a parent activity.
+        //If true, hoisting an expression so it is evaluated to a parent context will still generate code, but the values will be different.
+        //
+        //Therefore build up a list of selectors that are potentially problematic.
+        //Version 1:
+        //  Never hoist an expression that is dependent on them, but that is likely to prevent any expressions being
+        //  hoisted.
+        //Version 2:
+        //  Keep track of which of the selectors are definitely ambiguous.  Only prevent the expression being hoisted
+        //  if it depends on a selector, which would change meaning.  This may require us to walk the tree multiple times
+        //  since the first visit to an expression may be in a situation where it can be hoisted, but the second visit
+        //  when it can't.  Use a bitset to keep track of which selectors were ambiguous and only repeat if new selectors
+        //  are covered.
         if (selectors.empty())
             return;
 
-        unsigned first = getFirstActivityArgument(expr);
-        unsigned last = first + getNumActivityArguments(expr);
-        unsigned max = expr->numChildren();
-        unsigned i;
-        HqlExprCopyArray hiddenSelectors;
-        for (i = 0; i < first; i++)
-            expr->queryChild(i)->gatherTablesUsed(&hiddenSelectors, NULL);
-        for (i = last; i < max; i++)
-            expr->queryChild(i)->gatherTablesUsed(&hiddenSelectors, NULL);
-
-        ForEachItemIn(iSel, selectors)
-        {
-            IHqlExpression & cur = selectors.item(iSel);
-            if (hiddenSelectors.contains(cur))
-                ambiguousSelectors.append(cur);
-        }
-
-        switch (getChildDatasetType(expr))
-        {
-        case childdataset_datasetleft:
-        case childdataset_left:
-            {
-                IHqlExpression * ds = expr->queryChild(0);
-                IHqlExpression * selSeq = querySelSeq(expr);
-                OwnedHqlExpr left = createSelector(no_left, ds, selSeq);
-                if (selectors.contains(*left))
-                    ambiguousSelectors.append(*left);
-                break;
-            }
-        case childdataset_same_left_right:
-        case childdataset_top_left_right:
-        case childdataset_nway_left_right:
-            {
-                IHqlExpression * ds = expr->queryChild(0);
-                IHqlExpression * selSeq = querySelSeq(expr);
-                OwnedHqlExpr left = createSelector(no_left, ds, selSeq);
-                OwnedHqlExpr right = createSelector(no_right, ds, selSeq);
-                if (selectors.contains(*left))
-                    ambiguousSelectors.append(*left);
-                if (selectors.contains(*right))
-                    ambiguousSelectors.append(*right);
-                break;
-            }
-        case childdataset_leftright:
-            {
-                IHqlExpression * leftDs = expr->queryChild(0);
-                IHqlExpression * rightDs = expr->queryChild(1);
-                IHqlExpression * selSeq = querySelSeq(expr);
-                OwnedHqlExpr left = createSelector(no_left, leftDs, selSeq);
-                OwnedHqlExpr right = createSelector(no_right, rightDs, selSeq);
-                if (selectors.contains(*left))
-                    ambiguousSelectors.append(*left);
-                if (selectors.contains(*right))
-                    ambiguousSelectors.append(*right);
-                break;
-            }
-        }
+        unsigned minHidden;
+        calculateHidden(ambiguousMask, minHidden, expr);
     }
 
     bool isEvaluateable(IHqlExpression * ds, bool ignoreInline = false)
@@ -714,18 +801,9 @@ protected:
         if (isGrouped(ds) && selectors.ordinality() && !groupedChildIterators)
             return false;
 
-        //Check datasets are available
-        HqlExprCopyArray scopeUsed;
-        ds->gatherTablesUsed(NULL, &scopeUsed);
-        ForEachItemIn(i, scopeUsed)
-        {
-            IHqlExpression & cur = scopeUsed.item(i);
-            if (!selectors.contains(cur))
-                return false;
-
-            if (ambiguousSelectors.contains(cur))
-                return false;
-        }
+        EclChildSplitPointInfo * extra = queryExtra(ds->queryBody());
+        if (extra->neverHoist)
+            return false;
 
         if (!isEfficientToHoistDataset(ds, ignoreInline))
             return false;
@@ -761,10 +839,19 @@ protected:
         return true;
     }
 
+    ANewTransformInfo * createTransformInfo(IHqlExpression * expr)
+    {
+        return CREATE_NEWTRANSFORMINFO(EclChildSplitPointInfo, expr);
+    }
+
+    inline EclChildSplitPointInfo * queryExtra(IHqlExpression * expr) { return static_cast<EclChildSplitPointInfo *>(queryTransformExtra(expr)); }
+
+    inline bool tooManySelectors() const { return (selectors.ordinality() > sizeof(MaskType)*8); }
+
 protected:
     IHqlExpression * original;
     HqlExprCopyArray & selectors;
-    HqlExprCopyArray ambiguousSelectors;
+    MaskType ambiguousMask = 0;
     unsigned conditionalDepth;
     bool okToSelect;
     bool gathered;
@@ -1038,37 +1125,40 @@ void ActivityInvariantHoister::gatherChildSplitPoints(IHqlExpression * expr, Act
     EclChildSplitPointLocator locator(expr, activeSelectors, info->childDependents, options.groupedChildIterators);
     unsigned max = expr->numChildren();
 
-    //If child queries are supported then don't hoist the expressions if they might only be evaluated once
-    //because they may be conditional
-    bool alwaysOnce = false;
-    switch (expr->getOperator())
+    for (unsigned pass=0; pass < 2; pass++)
     {
-    case no_setresult:
-    case no_selectnth:
-        //set results, only done once=>don't hoist conditionals
-        locator.findSplitPoints(expr, last, max, true, true);
-        return;
-    case no_createrow:
-    case no_datasetfromrow:
-    case no_projectrow:
-        alwaysOnce = true;
-        break;
-    case no_loop:
-        if ((options.targetClusterType != RoxieCluster) && !options.isChildQuery)
-        {
-            //This is ugly!  The body is executed in parallel, so don't force that as a work unit result
-            //It means some child query expressions within loops don't get forced into work unit writes
-            //but that just means that the generated code will be not as good as it could be.
-            const unsigned bodyArg = 4;
-            locator.findSplitPoints(expr, 1, bodyArg, true, false);
-            locator.findSplitPoints(expr, bodyArg, bodyArg+1, false, false);
-            locator.findSplitPoints(expr, bodyArg+1, max, true, false);
-            return;
+        //If child queries are supported then don't hoist the expressions if they might only be evaluated once
+        //because they may be conditional
+        bool alwaysOnce = false;
+        switch (expr->getOperator())
+        {
+        case no_setresult:
+        case no_selectnth:
+            //set results, only done once=>don't hoist conditionals
+            locator.findSplitPoints(expr, last, max, true, true, pass);
+            continue;
+        case no_createrow:
+        case no_datasetfromrow:
+        case no_projectrow:
+            alwaysOnce = true;
+            break;
+        case no_loop:
+            if ((options.targetClusterType != RoxieCluster) && !options.isChildQuery)
+            {
+                //This is ugly!  The body is executed in parallel, so don't force that as a work unit result
+                //It means some child query expressions within loops don't get forced into work unit writes
+                //but that just means that the generated code will be not as good as it could be.
+                const unsigned bodyArg = 4;
+                locator.findSplitPoints(expr, 1, bodyArg, true, false, pass);
+                locator.findSplitPoints(expr, bodyArg, bodyArg+1, false, false, pass);
+                locator.findSplitPoints(expr, bodyArg+1, max, true, false, pass);
+                continue;
+            }
+            break;
         }
-        break;
+        locator.findSplitPoints(expr, 0, first, true, true, pass);          // IF() conditions only evaluated once... => don't force
+        locator.findSplitPoints(expr, last, max, true, alwaysOnce, pass);
     }
-    locator.findSplitPoints(expr, 0, first, true, true);          // IF() conditions only evaluated once... => don't force
-    locator.findSplitPoints(expr, last, max, true, alwaysOnce);
 }
 
 

+ 1 - 1
ecl/hqlcpp/hqlsource.cpp

@@ -832,7 +832,7 @@ bool SourceBuilder::isSourceInvariant(IHqlExpression * dataset, IHqlExpression *
         return true;
 
     HqlExprCopyArray inScope;
-    expr->gatherTablesUsed(NULL, &inScope);
+    expr->gatherTablesUsed(inScope);
     if (!canEvaluateInScope(parentCursors, inScope))
         return false;
 

+ 2 - 2
ecl/hqlcpp/hqlstmt.cpp

@@ -867,7 +867,7 @@ void BuildCtx::walkAssociations(AssocKind searchMask, IAssociationVisitor & visi
 HqlExprAssociation * BuildCtx::queryMatchExpr(IHqlExpression * search)
 {
     HqlExprCopyArray selectors;
-    search->gatherTablesUsed(NULL, &selectors);
+    search->gatherTablesUsed(selectors);
     return queryAssociation(search, AssocExpr, selectors.ordinality() ? &selectors : NULL);
 }
 
@@ -1045,7 +1045,7 @@ IHqlStmt * BuildCtx::selectBestContext(IHqlExpression * expr)
 
     //MORE: Access to global context, context and other things...
     HqlExprCopyArray inScope;
-    expr->gatherTablesUsed(NULL, &inScope);
+    expr->gatherTablesUsed(inScope);
 
     return recursiveGetBestContext(curStmts, inScope);
 }

+ 4 - 4
ecl/hqlcpp/hqlttcpp.cpp

@@ -9625,7 +9625,7 @@ void LeftRightSelectorNormalizer::analyseExpr(IHqlExpression * expr)
             {
                 IHqlExpression * dataset = expr->queryChild(0);
                 OwnedHqlExpr left = createSelector(no_left, dataset, selSeq);
-                gatherChildTablesUsed(NULL, &inScope, expr, 1);
+                gatherChildTablesUsed(inScope, expr, 1);
                 checkAmbiguity(inScope, left);
                 break;
             }
@@ -9633,7 +9633,7 @@ void LeftRightSelectorNormalizer::analyseExpr(IHqlExpression * expr)
             {
                 OwnedHqlExpr left = createSelector(no_left, expr->queryChild(0), selSeq);
                 OwnedHqlExpr right = createSelector(no_right, expr->queryChild(1), selSeq);
-                gatherChildTablesUsed(NULL, &inScope, expr, 2);
+                gatherChildTablesUsed(inScope, expr, 2);
                 checkAmbiguity(inScope, left);
                 checkAmbiguity(inScope, right);
                 break;
@@ -9645,7 +9645,7 @@ void LeftRightSelectorNormalizer::analyseExpr(IHqlExpression * expr)
                 IHqlExpression * dataset = expr->queryChild(0);
                 OwnedHqlExpr left = createSelector(no_left, dataset, selSeq);
                 OwnedHqlExpr right = createSelector(no_right, dataset, selSeq);
-                gatherChildTablesUsed(NULL, &inScope, expr, 1);
+                gatherChildTablesUsed(inScope, expr, 1);
                 checkAmbiguity(inScope, left);
                 checkAmbiguity(inScope, right);
                 break;
@@ -10565,7 +10565,7 @@ void HqlScopeTagger::reportError(WarnErrorCategory category, const char * msg, I
 void HqlScopeTagger::reportRootSelectorError(IHqlExpression * expr, IHqlExpression * transformed)
 {
     HqlExprCopyArray inScope;
-    transformed->gatherTablesUsed(nullptr, &inScope);
+    transformed->gatherTablesUsed(inScope);
     assertex(inScope.ordinality());
 
     //Recursively search for an expression which refers to the first selector that is unresolved.

+ 65 - 0
testing/regress/ecl/complexhoist5.ecl

@@ -0,0 +1,65 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+#option ('targetClusterType', 'roxie');
+
+//HOIST(dataset({unsigned i}) ds) := NOFOLD(SORT(NOFOLD(ds), i));
+HOIST( ds) := MACRO
+//NOFOLD(SORT(NOFOLD(ds), i))
+NOFOLD(ds)
+//ds
+ENDMACRO;
+
+//Very obscure code to trigger a problem in the code generator where an expression in a grandchild could theoretically
+//be evaluated in the child query, but it would have a different meaning.
+//In this case the SET(DATASET()) expression is dependent on LEFT(dsOuter).  That could be evaluated in the outer
+//project or the nexsted project.  When the graph for the child query is processed the code needs to ensure that
+//it is not hoisted.
+//Two solutions:
+//1. The child query is traversed, and for any operators that introduce the a selector that is already in scope all
+//   child expressions are marked to prevent hoisting.  (Even if the expression also occurs outside.)
+//2. Any expression that in introduces a selector that is already inscope is not transformed, but any selectors it
+//   contains are remapped.
+
+//The expression needs to be walked, and
+//
+//It generally requires LEFT rather than a dataset (e.g. complexhoist4) because when a dataset is resourced it
+//has a unique id appended - which disambiguates it from the child dataset.
+
+rec := { unsigned i };
+
+mkRow(unsigned value) := TRANSFORM(rec, SKIP(value = 1000000);   SELF.i := value);
+
+dsOuter  := HOIST(DATASET([1,2,3], rec, DISTRIBUTED));
+dsInner := HOIST(DATASET([0,1,2], rec, DISTRIBUTED));
+
+
+rec t1(unsigned x, rec l) := TRANSFORM,SKIP(x+l.i not in SET(DATASET(3, transform({ unsigned value}, SELF.value := COUNTER-1+(l.i-1)*2 )), value))
+    SELF := l;
+END;
+
+filteredOuter(unsigned x) := PROJECT(dsOuter, t1(x, LEFT));
+sumFilteredOuter(unsigned x) := AGGREGATE(filteredOuter(x), rec, transform(rec, SELF.i := (1<<(LEFT.i-1)) + RIGHT.i))[1].i;
+sumInnerx() := TABLE(dsInner, {i, sumFilteredOuter(i)});
+sumInner(unsigned x) := SUM(dsInner, x*sumFilteredOuter(i)<<i*3);
+projectOuter() := PROJECT(dsOuter, transform(rec, SELF.i := sumInner(1<<(LEFT.i-1)*16)));
+sumOuter() := SUM(NOFOLD(projectOuter()), i);
+
+sequential(
+//output(sumInner(1));   // 0b110111011 = 443
+output(sumOuter());    // 0b11011101100000001101110110000000110111011 = 1,902,699,545,019
+);

+ 49 - 0
testing/regress/ecl/complexhoist5b.ecl

@@ -0,0 +1,49 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+#option ('targetClusterType', 'roxie');
+
+//HOIST(dataset({unsigned i}) ds) := NOFOLD(SORT(NOFOLD(ds), i));
+HOIST( ds) := MACRO
+//NOFOLD(SORT(NOFOLD(ds), i))
+GLOBAL(ds,few)
+//ds
+ENDMACRO;
+
+rec := { unsigned i };
+
+mkRow(unsigned value) := TRANSFORM(rec, SKIP(value = 1000000);   SELF.i := value);
+
+dsOuter  := HOIST(DATASET([1,2,3], rec, DISTRIBUTED));
+dsInner := HOIST(DATASET([0,1,2], rec, DISTRIBUTED));
+
+
+rec t1(unsigned x, rec l) := TRANSFORM,SKIP(x+l.i not in SET(DATASET(3, transform({ unsigned value}, SELF.value := COUNTER-1+(l.i-1)*2 )), value))
+    SELF := l;
+END;
+
+filteredOuter(unsigned x) := PROJECT(dsOuter, t1(x, LEFT));
+sumFilteredOuter(unsigned x) := AGGREGATE(filteredOuter(x), rec, transform(rec, SELF.i := (1<<(LEFT.i-1)) + RIGHT.i))[1].i;
+sumInnerx() := TABLE(dsInner, {i, sumFilteredOuter(i)});
+sumInner(unsigned x) := SUM(dsInner, x*sumFilteredOuter(i)<<i*3);
+projectOuter() := PROJECT(dsOuter, transform(rec, SELF.i := sumInner(1<<(LEFT.i-1)*16)));
+sumOuter() := SUM(NOFOLD(projectOuter()), i);
+
+sequential(
+//output(sumInner(1));   // 0b110111011 = 443
+output(sumOuter());    // 0b11011101100000001101110110000000110111011 = 1,902,699,545,019
+);

+ 3 - 0
testing/regress/ecl/key/complexhoist5.xml

@@ -0,0 +1,3 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>1902699545019</Result_1></Row>
+</Dataset>

+ 3 - 0
testing/regress/ecl/key/complexhoist5b.xml

@@ -0,0 +1,3 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>1902699545019</Result_1></Row>
+</Dataset>