Procházet zdrojové kódy

HPCC-9307 initial work and refactoring of action lists

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday před 12 roky
rodič
revize
47b6659b98

+ 3 - 1
ecl/hql/hqlattr.cpp

@@ -532,6 +532,7 @@ unsigned getOperatorMetaFlags(node_operator op)
     case no_sequential:
     case no_parallel:
     case no_actionlist:
+    case no_orderedactionlist:
     case no_soapaction_ds:
     case no_newsoapaction_ds:
     case no_keydiff:
@@ -620,7 +621,7 @@ unsigned getOperatorMetaFlags(node_operator op)
 
     case no_unused6:
     case no_unused13: case no_unused14: case no_unused15:
-    case no_unused24: case no_unused25: case no_unused28: case no_unused29:
+    case no_unused25: case no_unused28: case no_unused29:
     case no_unused30: case no_unused31: case no_unused32: case no_unused33: case no_unused34: case no_unused35: case no_unused36: case no_unused37: case no_unused38:
     case no_unused40: case no_unused41: case no_unused42: case no_unused43: case no_unused44: case no_unused45: case no_unused46: case no_unused47: case no_unused48: case no_unused49:
     case no_unused50: case no_unused52:
@@ -2089,6 +2090,7 @@ bool containsAnyActions(IHqlExpression * expr)
     case no_comma:
     case no_compound:
     case no_actionlist:
+    case no_orderedactionlist:
         {
             ForEachChild(i, expr)
             {

+ 29 - 5
ecl/hql/hqlexpr.cpp

@@ -1442,10 +1442,11 @@ const char *getOpString(node_operator op)
     case no_assertconcrete: return "no_assertconcrete";
     case no_unboundselect: return "no_unboundselect";
     case no_id: return "no_id";
+    case no_orderedactionlist: return "ORDERED";
 
     case no_unused6:
     case no_unused13: case no_unused14: case no_unused15:
-    case no_unused24: case no_unused25: case no_unused28: case no_unused29:
+    case no_unused25: case no_unused28: case no_unused29:
     case no_unused30: case no_unused31: case no_unused32: case no_unused33: case no_unused34: case no_unused35: case no_unused36: case no_unused37: case no_unused38:
     case no_unused40: case no_unused41: case no_unused42: case no_unused43: case no_unused44: case no_unused45: case no_unused46: case no_unused47: case no_unused48: case no_unused49:
     case no_unused50: case no_unused52:
@@ -2258,6 +2259,7 @@ inline unsigned doGetNumChildTables(IHqlExpression * dataset)
         return 0;
     case no_sequential:
     case no_parallel:
+    case no_orderedactionlist:
         return 0;
     case no_quoted:
     case no_variable:
@@ -12297,7 +12299,7 @@ extern IHqlExpression * createCompound(const HqlExprArray & actions)
     return expr;
 }
 
-extern IHqlExpression * createActionList(const HqlExprArray & actions)
+extern IHqlExpression * createActionList(node_operator op, const HqlExprArray & actions)
 {
     switch (actions.ordinality())
     {
@@ -12306,7 +12308,12 @@ extern IHqlExpression * createActionList(const HqlExprArray & actions)
     case 1:
         return LINK(&actions.item(0));
     }
-    return createValueSafe(no_actionlist, makeVoidType(), actions);
+    return createValueSafe(op, makeVoidType(), actions);
+}
+
+extern IHqlExpression * createActionList(const HqlExprArray & actions)
+{
+    return createActionList(no_actionlist, actions);
 }
 
 extern void ensureActions(HqlExprArray & actions, unsigned first, unsigned last)
@@ -12324,7 +12331,7 @@ extern void ensureActions(HqlExprArray & actions)
     ensureActions(actions, 0, actions.ordinality());
 }
 
-extern IHqlExpression * createActionList(const HqlExprArray & actions, unsigned from, unsigned to)
+extern IHqlExpression * createActionList(node_operator op, const HqlExprArray & actions, unsigned from, unsigned to)
 {
     switch (to-from)
     {
@@ -12333,7 +12340,12 @@ extern IHqlExpression * createActionList(const HqlExprArray & actions, unsigned
     case 1:
         return LINK(&actions.item(from));
     }
-    return createValueSafe(no_actionlist, makeVoidType(), actions, from, to);
+    return createValueSafe(op, makeVoidType(), actions, from, to);
+}
+
+extern IHqlExpression * createActionList(const HqlExprArray & actions, unsigned from, unsigned to)
+{
+    return createActionList(no_actionlist, actions, from, to);
 }
 
 extern IHqlExpression * createCompound(node_operator op, const HqlExprArray & actions)
@@ -13164,6 +13176,7 @@ static IHqlExpression * walkInstantEclTransformations(IHqlExpression * expr, uns
     case no_comma:
     case no_sequential:
     case no_parallel:
+    case no_orderedactionlist:
     case no_actionlist:
     case no_if:
     case no_case:
@@ -14963,6 +14976,17 @@ bool activityMustBeCompound(IHqlExpression * expr)
     return false;
 }
 
+bool isSequentialActionList(IHqlExpression * expr)
+{
+    switch (expr->getOperator())
+    {
+    case no_orderedactionlist:
+    case no_sequential:
+        return true;
+    }
+    return false;
+}
+
 bool isSelectFirstRow(IHqlExpression * expr)
 {
     if (expr->getOperator() != no_selectnth)

+ 4 - 1
ecl/hql/hqlexpr.hpp

@@ -352,7 +352,7 @@ enum _node_operator {
         no_assertconcrete,
         no_unboundselect,
         no_id,
-    no_unused24,
+        no_orderedactionlist,
         no_dataset_from_transform,
         no_childquery,
         no_unknown,
@@ -1329,7 +1329,9 @@ extern HQL_API IHqlExpression * createCompound(IHqlExpression * expr1, IHqlExpre
 extern HQL_API IHqlExpression * createCompound(const HqlExprArray & actions);
 extern HQL_API IHqlExpression * createCompound(node_operator op, const HqlExprArray & actions);
 extern HQL_API IHqlExpression * createActionList(const HqlExprArray & actions);
+extern HQL_API IHqlExpression * createActionList(node_operator op, const HqlExprArray & actions);
 extern HQL_API IHqlExpression * createActionList(const HqlExprArray & actions, unsigned from, unsigned to);
+extern HQL_API IHqlExpression * createActionList(node_operator op, const HqlExprArray & actions, unsigned from, unsigned to);
 extern HQL_API IHqlExpression * createComma(IHqlExpression * expr1, IHqlExpression * expr2);
 extern HQL_API IHqlExpression * createComma(IHqlExpression * expr1, IHqlExpression * expr2, IHqlExpression * expr3);
 extern HQL_API IHqlExpression * createComma(IHqlExpression * expr1, IHqlExpression * expr2, IHqlExpression * expr3, IHqlExpression * expr4);
@@ -1731,6 +1733,7 @@ extern bool canBeVirtual(IHqlExpression * expr);
 extern bool areAllBasesFullyBound(IHqlExpression * module);
 extern HQL_API bool isUpdatedConditionally(IHqlExpression * expr);
 extern HQL_API bool activityMustBeCompound(IHqlExpression * expr);
+extern HQL_API bool isSequentialActionList(IHqlExpression * expr);
 extern HQL_API unsigned queryCurrentTransformDepth();                   // debugging - only valid inside a transform
 extern HQL_API bool isExternalFunction(IHqlExpression * funcdef);
 extern HQL_API bool isEmbedFunction(IHqlExpression * expr);

+ 3 - 1
ecl/hql/hqlfold.cpp

@@ -3305,6 +3305,7 @@ IHqlExpression * foldConstantOperator(IHqlExpression * expr, unsigned foldOption
             break;
         }
     case no_actionlist:
+    case no_orderedactionlist:
         {
             bool same = true;
             HqlExprArray args;
@@ -3324,7 +3325,7 @@ IHqlExpression * foldConstantOperator(IHqlExpression * expr, unsigned foldOption
                 }
             }
             if (!same)
-                return createActionList(args);
+                return createActionList(op, args);
             break;
         }
     case no_exists:
@@ -4846,6 +4847,7 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
         break;
     case no_sequential:
     case no_parallel:
+    case no_orderedactionlist:
         if (expr->numChildren() == 1)
         {
             if (expr->queryChild(0)->isAttribute())

+ 7 - 0
ecl/hql/hqlgram.y

@@ -327,6 +327,7 @@ static void eclsyntaxerror(HqlGram * parser, const char * s, short yystate, int
   ONWARNING
   OPT
   OR
+  ORDERED
   OUTER
   OUTPUT
   TOK_OUT
@@ -2506,6 +2507,12 @@ actionStmt
                             parser->endList(actions);
                             $$.setExpr(createValue(no_parallel, makeVoidType(), actions), $1);
                         }
+    | ORDERED '(' beginList sequentialActionlist optSemiComma ')'
+                        {
+                            HqlExprArray actions;
+                            parser->endList(actions);
+                            $$.setExpr(createValue(no_orderedactionlist, makeVoidType(), actions), $1);
+                        }
     | SOAPCALL '(' expression ',' expression ',' recordDef ')'
                         {
                             parser->normalizeExpression($3, type_stringorunicode, false);

+ 1 - 0
ecl/hql/hqlgram2.cpp

@@ -10351,6 +10351,7 @@ static void getTokenText(StringBuffer & msg, int token)
     case OPT: msg.append("OPT"); break;
     case OR : msg.append("OR "); break;
     case ORDER: msg.append("ORDER"); break;
+    case ORDERED: msg.append("ORDERED"); break;
     case OUTER: msg.append("OUTER"); break;
     case OUTPUT: msg.append("OUTPUT"); break;
     case TOK_OUT: msg.append("OUT"); break;

+ 3 - 1
ecl/hql/hqlir.cpp

@@ -273,7 +273,7 @@ const char * getOperatorIRText(node_operator op)
     EXPAND_CASE(no,assertconcrete);
     EXPAND_CASE(no,unboundselect);
     EXPAND_CASE(no,id);
-    EXPAND_CASE(no,unused24);
+    EXPAND_CASE(no,orderedactionlist);
     EXPAND_CASE(no,dataset_from_transform);
     EXPAND_CASE(no,childquery);
     EXPAND_CASE(no,unknown);
@@ -739,6 +739,8 @@ inline type_t getRequiredTypeCode(node_operator op)
     case no_sequential:
     case no_parallel:
     case no_apply:
+    case no_actionlist:
+    case no_orderedactionlist:
         return type_void;
     case no_attr:
     case no_attr_expr:

+ 1 - 0
ecl/hql/hqllex.l

@@ -821,6 +821,7 @@ ONFAIL              { RETURNSYM(ONFAIL); }
 ONLY                { RETURNSYM(ONLY); }
 ONWARNING           { RETURNSYM(ONWARNING); }
 OPT                 { RETURNSYM(OPT); }
+ORDERED             { RETURNSYM(ORDERED); }
 OUTER               { RETURNSYM(OUTER); }
 OUTPUT              { 
                         //RETURNSYM(OUTPUT);

+ 2 - 0
ecl/hql/hqlutil.cpp

@@ -1855,6 +1855,7 @@ unsigned getNumActivityArguments(IHqlExpression * expr)
         return 1;
     case no_sequential:
     case no_parallel:
+    case no_orderedactionlist:
     case no_actionlist:
     case no_comma:
     case no_compound:
@@ -1975,6 +1976,7 @@ bool isSinkActivity(IHqlExpression * expr)
     case no_parallel:
     case no_actionlist:
     case no_sequential:
+    case no_orderedactionlist:
     case no_apply:
     case no_output:
     case no_buildindex:

+ 2 - 0
ecl/hqlcpp/hqlcpp.cpp

@@ -1563,6 +1563,7 @@ void HqlCppTranslator::cacheOptions()
         DebugOption(options.resourceSequential,"resourceSequential", false),
         DebugOption(options.workunitTemporaries,"workunitTemporaries", true),
         DebugOption(options.resourceConditionalActions,"resourceConditionalActions", false),  //targetRoxie() ??
+        DebugOption(options.actionLinkInNewGraph,"actionLinkInNewGraph",true),
         DebugOption(options.minimizeWorkunitTemporaries, "<exception>", false),
         DebugOption(options.pickBestEngine,"pickBestEngine", true),
         DebugOption(options.groupedChildIterators,"groupedChildIterators", false),
@@ -3571,6 +3572,7 @@ void HqlCppTranslator::buildStmt(BuildCtx & _ctx, IHqlExpression * expr)
     case no_parallel:
     case no_sequential:
     case no_actionlist:
+    case no_orderedactionlist:
         {
             ForEachChild(idx, expr)
             {

+ 3 - 0
ecl/hqlcpp/hqlcpp.ipp

@@ -727,6 +727,7 @@ struct HqlCppOptions
     bool                optimizeParentAccess;
     bool                expandPersistInputDependencies;
     bool                expirePersists;
+    bool                actionLinkInNewGraph;
 };
 
 //Any information gathered while processing the query should be moved into here, rather than cluttering up the translator class
@@ -1533,6 +1534,8 @@ public:
     IHqlExpression * queryExpandAliasScope(BuildCtx & ctx, IHqlExpression * expr);
 
     void addDependency(BuildCtx & ctx, ABoundActivity * element, ABoundActivity * dependent, IAtom * kind, const char * label=NULL, int controlId = 0);
+    void addDependency(BuildCtx & ctx, ABoundActivity * element, ActivityInstance * instance, IAtom * kind, const char * label=NULL, int controlId = 0);
+    void addDependency(BuildCtx & ctx, ABoundActivity * sourceActivity, IPropertyTree * sinkGraph, ABoundActivity * sinkActivity, IAtom * kind, const char * label, int whenId);
     void addFileDependency(IHqlExpression * name, ABoundActivity * whoAmI);
 
     void doBuildClearAggregateRecord(BuildCtx & ctx, IHqlExpression * record, IHqlExpression * self, IHqlExpression * transform);

+ 6 - 16
ecl/hqlcpp/hqlcppds.cpp

@@ -1323,14 +1323,12 @@ unsigned ChildGraphExprBuilder::addInput()
     return id;
 }
 
-IHqlExpression * ChildGraphExprBuilder::getGraph(IAtom * extraAttrName)
+IHqlExpression * ChildGraphExprBuilder::getGraph(node_operator listOp)
 {
     HqlExprArray args;
     args.append(*LINK(represents));
     args.append(*getSizetConstant(numResults()));
-    args.append(*createActionList(results));
-    if (extraAttrName)
-        args.append(*createAttribute(extraAttrName));
+    args.append(*createActionList(listOp, results));
     return createValue(no_childquery, makeVoidType(), args);
 }
 
@@ -1644,7 +1642,8 @@ IHqlExpression * HqlCppTranslator::getResourcedChildGraph(BuildCtx & ctx, IHqlEx
         DBGLOG("Before resourcing a child graph");
 
     IHqlExpression * graphIdExpr = childQuery->queryChild(0);
-    LinkedHqlExpr resourced = childQuery->queryChild(2);
+    IHqlExpression * originalQuery = childQuery->queryChild(2);
+    LinkedHqlExpr resourced = originalQuery;
     checkNormalized(ctx, resourced);
 
     unsigned csfFlags = CSFindex|options.optimizeDiskFlag;
@@ -1685,10 +1684,10 @@ IHqlExpression * HqlCppTranslator::getResourcedChildGraph(BuildCtx & ctx, IHqlEx
     if (graphKind == no_loop)
     {
         bool insideChild = insideChildQuery(ctx);
-        resourced.setown(resourceLoopGraph(*this, activeRows, resourced, targetClusterType, graphIdExpr, &numResults, insideChild));
+        resourced.setown(resourceLoopGraph(*this, activeRows, resourced, targetClusterType, graphIdExpr, numResults, insideChild));
     }
     else
-        resourced.setown(resourceNewChildGraph(*this, activeRows, resourced, targetClusterType, graphIdExpr, &numResults, childQuery->hasAttribute(sequentialAtom)));
+        resourced.setown(resourceNewChildGraph(*this, activeRows, resourced, targetClusterType, graphIdExpr, numResults));
 
     DEBUG_TIMER("EclServer: resource graph", msTick()-time);
     checkNormalized(ctx, resourced);
@@ -1725,18 +1724,9 @@ IHqlExpression * HqlCppTranslator::getResourcedChildGraph(BuildCtx & ctx, IHqlEx
         DEBUG_TIMER("EclServer: optimize graph", msTick()-time);
     }
 
-    if (numResults == 0) numResults++;
-
-    HqlExprArray children;
-    resourced->unwindList(children, no_actionlist);
-    children.append(*createAttribute(numResultsAtom, getSizetConstant(numResults)));
-    children.append(*LINK(graphIdExpr));
-    resourced.setown(createValue(no_subgraph, makeVoidType(), children));
-
     if (options.paranoidCheckNormalized || options.paranoidCheckDependencies)
         DBGLOG("After resourcing a child graph");
 
-    resourced.setown(inheritAttribute(resourced, childQuery, sequentialAtom));
     return resourced.getClear();
 }
 

+ 1 - 1
ecl/hqlcpp/hqlcppds.hpp

@@ -27,7 +27,7 @@ public:
     IHqlExpression * addDataset(IHqlExpression * expr);
     void addAction(IHqlExpression * expr);
     unsigned addInput();
-    IHqlExpression * getGraph(IAtom * extraAttrName = NULL);
+    IHqlExpression * getGraph(node_operator listOp = no_actionlist);
 
     inline IHqlExpression * queryRepresents() const { return represents; }
     inline unsigned numResults() const { return numInputs + numOutputs; }

+ 31 - 22
ecl/hqlcpp/hqlgraph.cpp

@@ -66,48 +66,57 @@ void addGraphAttributeBool(IPropertyTree * node, const char * name, bool value)
         addGraphAttribute(node, name)->setPropBool("@value", value);
 }
 
-void addSimpleGraphEdge(IPropertyTree * subGraph, unsigned __int64 source, unsigned __int64 target, unsigned outputIndex, unsigned inputIndex, IAtom * kind, const char * label, bool nWay)
+IPropertyTree * addIntraGraphEdge(IPropertyTree * subGraph, unsigned __int64 source, unsigned __int64 target, unsigned outputIndex)
 {
     IPropertyTree *edge = createPTree();
     edge->setPropInt64("@target", target);
     edge->setPropInt64("@source", source);
-    if (outputIndex != 0)
-        addGraphAttributeInt(edge, "_sourceIndex", outputIndex);
-    if (inputIndex != 0)
-        addGraphAttributeInt(edge, "_targetIndex", inputIndex);
-    if (label)
-        edge->setProp("@label", label);
-
-    if (kind == dependencyAtom)
-        addGraphAttributeBool(edge, "_dependsOn", true);
-
-    if (nWay)
-        edge->setPropBool("@nWay", true);
 
     StringBuffer s;
     edge->setProp("@id", s.append(source).append('_').append(outputIndex).str());
-    subGraph->addPropTree("edge", edge);
+    return subGraph->addPropTree("edge", edge);
 }
 
-
-void addComplexGraphEdge(IPropertyTree * graph, unsigned __int64 sourceGraph, unsigned __int64 targetGraph, unsigned __int64 sourceActivity, unsigned __int64 targetActivity, unsigned outputIndex, IAtom * kind, const char * label)
+IPropertyTree * addInterGraphEdge(IPropertyTree * graph, unsigned __int64 sourceGraph, unsigned __int64 targetGraph, unsigned __int64 sourceActivity, unsigned __int64 targetActivity, unsigned outputIndex)
 {
     StringBuffer idText;
     IPropertyTree *edge = createPTree();
     edge->setProp("@id", idText.clear().append(sourceGraph).append('_').append(targetGraph).append("_").append(outputIndex).str());
     edge->setPropInt64("@target", sourceGraph);
     edge->setPropInt64("@source", targetGraph);
+
+    addGraphAttributeInt(edge, "_sourceActivity", sourceActivity);
+    addGraphAttributeInt(edge, "_targetActivity", targetActivity);
+    return graph->addPropTree("edge", edge);
+}
+
+void setEdgeAttributes(IPropertyTree * edge, unsigned outputIndex, unsigned inputIndex, IAtom * kind, const char * label, bool nWay)
+{
+    if (outputIndex != 0)
+        addGraphAttributeInt(edge, "_sourceIndex", outputIndex);
+    if (inputIndex != 0)
+        addGraphAttributeInt(edge, "_targetIndex", inputIndex);
     if (label)
         edge->setProp("@label", label);
-    if (outputIndex)
-        addGraphAttributeInt(edge, "_sourceIndex", outputIndex);
-
     if (kind == dependencyAtom)
         addGraphAttributeBool(edge, "_dependsOn", true);
+    if (nWay)
+        edge->setPropBool("@nWay", true);
+}
 
-    addGraphAttributeInt(edge, "_sourceActivity", sourceActivity);
-    addGraphAttributeInt(edge, "_targetActivity", targetActivity);
-    graph->addPropTree("edge", edge);
+
+IPropertyTree * addSimpleGraphEdge(IPropertyTree * subGraph, unsigned __int64 source, unsigned __int64 target, unsigned outputIndex, unsigned inputIndex, IAtom * kind, const char * label, bool nWay)
+{
+    IPropertyTree *edge = addIntraGraphEdge(subGraph, source, target, outputIndex);
+    setEdgeAttributes(edge, outputIndex, inputIndex, kind, label, nWay);
+    return edge;
+}
+
+IPropertyTree * addComplexGraphEdge(IPropertyTree * graph, unsigned __int64 sourceGraph, unsigned __int64 targetGraph, unsigned __int64 sourceActivity, unsigned __int64 targetActivity, unsigned outputIndex, IAtom * kind, const char * label)
+{
+    IPropertyTree * edge = addInterGraphEdge(graph, sourceGraph, targetGraph, sourceActivity, targetActivity, outputIndex);
+    setEdgeAttributes(edge, outputIndex, 0, kind, label, false);
+    return edge;
 }
 
 

+ 2 - 2
ecl/hqlcpp/hqlgraph.ipp

@@ -108,8 +108,8 @@ IPropertyTree * addGraphAttribute(IPropertyTree * node, const char * name);
 void addGraphAttribute(IPropertyTree * node, const char * name, const char * value);
 void addGraphAttributeInt(IPropertyTree * node, const char * name, __int64 value);
 void addGraphAttributeBool(IPropertyTree * node, const char * name, bool value);
-void addSimpleGraphEdge(IPropertyTree * subGraph, unsigned __int64 source, unsigned __int64 target, unsigned outputIndex, unsigned inputIndex, IAtom * kind, const char * label, bool nWay);
-void addComplexGraphEdge(IPropertyTree * graph, unsigned __int64 sourceGraph, unsigned __int64 targetGraph, unsigned __int64 sourceActivity, unsigned __int64 targetActivity, unsigned outputIndex, IAtom * kind, const char * label);
+IPropertyTree * addSimpleGraphEdge(IPropertyTree * subGraph, unsigned __int64 source, unsigned __int64 target, unsigned outputIndex, unsigned inputIndex, IAtom * kind, const char * label, bool nWay);
+IPropertyTree * addComplexGraphEdge(IPropertyTree * graph, unsigned __int64 sourceGraph, unsigned __int64 targetGraph, unsigned __int64 sourceActivity, unsigned __int64 targetActivity, unsigned outputIndex, IAtom * kind, const char * label);
 void removeGraphAttribute(IPropertyTree * node, const char * name);
 
 #endif

+ 58 - 34
ecl/hqlcpp/hqlhtcpp.cpp

@@ -1712,6 +1712,8 @@ ActivityInstance::ActivityInstance(HqlCppTranslator & _translator, BuildCtx & ct
     meta.setMeta(translator, record, ::isGrouped(outputDataset));
 
     activityId = translator.nextActivityId();
+    if (activityId == 1238)
+        dataset->numChildren();
 
     StringBuffer s;
     className.set(s.clear().append("cAc").append(activityId).str());
@@ -5734,6 +5736,7 @@ double HqlCppTranslator::getComplexity(IHqlExpression * expr, ClusterType cluste
     case no_compound:
     case no_parallel:
     case no_actionlist:
+    case no_orderedactionlist:
         break;
     case no_thor:
         {
@@ -6511,6 +6514,7 @@ ABoundActivity * HqlCppTranslator::buildActivity(BuildCtx & ctx, IHqlExpression
             case no_parallel:
             case no_sequential:
             case no_actionlist:
+            case no_orderedactionlist:
                 result = doBuildActivitySequentialParallel(ctx, expr, isRoot);
                 break;
             case no_activerow:
@@ -6632,17 +6636,17 @@ void HqlCppTranslator::buildRootActivity(BuildCtx & ctx, IHqlExpression * expr)
 {
     switch (expr->getOperator())
     {
-    case no_compound:
-    case no_parallel:
-    case no_actionlist:
+//    case no_compound:
+//    case no_parallel:
+//    case no_actionlist:
         {
             ForEachChild(idx, expr)
                 buildRootActivity(ctx, expr->queryChild(idx));
             break;
         }
-    case no_null:
-        if (expr->isAction())
-            return;
+//    case no_null:
+//        if (expr->isAction())
+//            return;
         //fall through
     default:
         {
@@ -6923,10 +6927,16 @@ BoundRow * HqlCppTranslator::resolveSelectorDataset(BuildCtx & ctx, IHqlExpressi
 
 //---------------------------------------------------------------------------
 
-void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, ABoundActivity * dependent, IAtom * kind, const char * label, int whenId)
+void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * sourceActivity, IPropertyTree * sinkGraphTree, ABoundActivity * sinkActivity, IAtom * kind, const char * label, int whenId)
 {
-    ABoundActivity * sourceActivity = element;
-    ABoundActivity * sinkActivity = dependent;
+    IPropertyTree * graphTree = NULL;
+    if (sinkActivity->queryGraphId() == sourceActivity->queryGraphId())
+    {
+        if (targetHThor())
+            throwError1(HQLERR_DependencyWithinGraph, sinkActivity->queryGraphId());
+        graphTree = sinkGraphTree;
+    }
+
     unsigned outputIndex = 0;
     if (kind != childAtom)
         outputIndex = sourceActivity->nextOutputCount();
@@ -6946,13 +6956,6 @@ void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, A
 
     IPropertyTree *edge = createPTree();
     edge->setProp("@id", idText.str());
-    edge->setPropInt64("@target", sinkActivity->queryGraphId());
-    edge->setPropInt64("@source", sourceActivity->queryGraphId());
-    if (targetHThor())
-    {
-        if (sinkActivity->queryGraphId() == sourceActivity->queryGraphId())
-            throwError1(HQLERR_DependencyWithinGraph, sinkActivity->queryGraphId());
-    }
     if (label)
         edge->setProp("@label", label);
     if (targetRoxie())
@@ -6971,7 +6974,7 @@ void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, A
     }
     else if (sourceActivity->queryContainerId() != sinkActivity->queryContainerId())
     {
-        //mark as a dependendency if the source and target aren't at the same depth
+        //mark as a dependency if the source and target aren't at the same depth
         addGraphAttributeBool(edge, "_dependsOn", true);
     }
 
@@ -6979,9 +6982,30 @@ void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, A
         addGraphAttributeInt(edge, "_when", whenId);
 
 
-    addGraphAttributeInt(edge, "_sourceActivity", sourceActivity->queryActivityId());
-    addGraphAttributeInt(edge, "_targetActivity", sinkActivity->queryActivityId());
-    activeGraph->xgmml->addPropTree("edge", edge);
+    if (graphTree)
+    {
+        edge->setPropInt64("@target", sinkActivity->queryActivityId());
+        edge->setPropInt64("@source", sourceActivity->queryActivityId());
+        graphTree->addPropTree("edge", edge);
+    }
+    else
+    {
+        edge->setPropInt64("@target", sinkActivity->queryGraphId());
+        edge->setPropInt64("@source", sourceActivity->queryGraphId());
+        addGraphAttributeInt(edge, "_sourceActivity", sourceActivity->queryActivityId());
+        addGraphAttributeInt(edge, "_targetActivity", sinkActivity->queryActivityId());
+        activeGraph->xgmml->addPropTree("edge", edge);
+    }
+}
+
+void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, ABoundActivity * dependent, IAtom * kind, const char * label, int whenId)
+{
+    addDependency(ctx, element, NULL, dependent, kind, label, whenId);
+}
+
+void HqlCppTranslator::addDependency(BuildCtx & ctx, ABoundActivity * element, ActivityInstance * instance, IAtom * kind, const char * label, int whenId)
+{
+    addDependency(ctx, element, instance->querySubgraphNode(), instance->queryBoundActivity(), kind, label, whenId);
 }
 
 void HqlCppTranslator::buildClearRecord(BuildCtx & ctx, IHqlExpression * dataset, IHqlExpression * record, int direction)
@@ -7495,6 +7519,7 @@ static bool isFilePersist(IHqlExpression * expr)
         case no_output:
             return (queryRealChild(expr, 1) != NULL);
         case no_actionlist:
+        case no_orderedactionlist:
             expr = expr->queryChild(expr->numChildren()-1);
             break;
         default:
@@ -8646,7 +8671,7 @@ public:
     {
         if (builder)
         {
-            OwnedHqlExpr childquery = builder->getGraph(sequentialAtom);
+            OwnedHqlExpr childquery = builder->getGraph(no_orderedactionlist);
             translator.buildStmt(ctx, childquery);
             builder.clear();
         }
@@ -8662,6 +8687,7 @@ public:
         case no_parallel:
         case no_sequential:
         case no_actionlist:
+        case no_orderedactionlist:
         case no_compound:
             {
                 ForEachChild(idx, expr)
@@ -8695,6 +8721,7 @@ public:
         case no_parallel:
         case no_sequential:
         case no_actionlist:
+        case no_orderedactionlist:
         case no_compound:
             {
                 ForEachChild(idx, expr)
@@ -9081,12 +9108,8 @@ IHqlExpression * HqlCppTranslator::getResourcedGraph(IHqlExpression * expr, IHql
     if (outputLibraryId)
     {
         unsigned numResults = outputLibrary->numResultsUsed();
-        resourced.setown(resourceLibraryGraph(*this, resourced, targetClusterType, numNodes, outputLibraryId, &numResults));
-        HqlExprArray children;
-        unwindCommaCompound(children, resourced);
-        children.append(*createAttribute(numResultsAtom, getSizetConstant(numResults)));
-        children.append(*createAttribute(multiInstanceAtom));       // since can be called from multiple places.
-        resourced.setown(createValue(no_subgraph, makeVoidType(), children));
+        resourced.setown(resourceLibraryGraph(*this, resourced, targetClusterType, numNodes, outputLibraryId, numResults));
+        resourced.setown(appendAttribute(resourced, multiInstanceAtom));  // since can be called from multiple places.
     }
     else
         resourced.setown(resourceThorGraph(*this, resourced, targetClusterType, numNodes, graphIdExpr));
@@ -14764,10 +14787,10 @@ ABoundActivity * HqlCppTranslator::doBuildActivityExecuteWhen(BuildCtx & ctx, IH
     buildInstanceSuffix(instance);
 
     if (expr->isAction())
-        addDependency(ctx, boundDataset, instance->queryBoundActivity(), dependencyAtom, NULL, 1);
+        addDependency(ctx, boundDataset, instance, dependencyAtom, NULL, 1);
     else
         buildConnectInputOutput(ctx, instance, boundDataset, 0, 0);
-    addDependency(ctx, associatedActivity, instance->queryBoundActivity(), dependencyAtom, label, when);
+    addDependency(ctx, associatedActivity, instance, dependencyAtom, label, when);
 
     return instance->getBoundActivity();
 }
@@ -15687,9 +15710,9 @@ ABoundActivity * HqlCppTranslator::doBuildActivityIf(BuildCtx & ctx, IHqlExpress
         if (expr->isAction())
         {
             if (boundTrue)
-                addDependency(ctx, boundTrue, instance->queryBoundActivity(), dependencyAtom, firstLabel, 1);
+                addDependency(ctx, boundTrue, instance, dependencyAtom, firstLabel, 1);
             if (boundFalse)
-                addDependency(ctx, boundFalse, instance->queryBoundActivity(), dependencyAtom, "False", 2);
+                addDependency(ctx, boundFalse, instance, dependencyAtom, "False", 2);
         }
         else
         {
@@ -15734,7 +15757,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivitySequentialParallel(BuildCtx &
         ABoundActivity & cur = (ABoundActivity&)boundActivities.item(j);
         StringBuffer temp;
         temp.append("Action #").append(j+1);
-        addDependency(ctx, &cur, instance->queryBoundActivity(), dependencyAtom, temp.str(), j+1);
+        addDependency(ctx, &cur, instance, dependencyAtom, temp.str(), j+1);
     }
 
     buildInstanceSuffix(instance);
@@ -15768,7 +15791,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityChoose(BuildCtx & ctx, IHqlExp
         ABoundActivity * boundBranch = &inputs.item(branchIdx);
         label.clear().append("Branch ").append(branchIdx+1);
         if (expr->isAction())
-            addDependency(ctx, boundBranch, instance->queryBoundActivity(), dependencyAtom, label.str(), branchIdx+1);
+            addDependency(ctx, boundBranch, instance, dependencyAtom, label.str(), branchIdx+1);
         else
             buildConnectInputOutput(ctx, instance, boundBranch, 0, branchIdx, label.str());
     }
@@ -15843,7 +15866,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityCase(BuildCtx & ctx, IHqlExpre
             label.clear().append("Branch ").append(branchIdx+1);
 
         if (expr->isAction())
-            addDependency(ctx, boundBranch, instance->queryBoundActivity(), dependencyAtom, label.str(), branchIdx+1);
+            addDependency(ctx, boundBranch, instance, dependencyAtom, label.str(), branchIdx+1);
         else
             buildConnectInputOutput(ctx, instance, boundBranch, 0, idx-first, label.str());
     }
@@ -18222,6 +18245,7 @@ static bool needsRealThor(IHqlExpression *expr, unsigned flags)
     case no_nofold:
     case no_nohoist:
     case no_actionlist:
+    case no_orderedactionlist:
     case no_externalcall:
     case no_call:
     case no_compound_fetch:

+ 1 - 0
ecl/hqlcpp/hqlinline.cpp

@@ -727,6 +727,7 @@ GraphLocalisation queryActivityLocalisation(IHqlExpression * expr, bool optimize
     case no_deserialize:
     case no_serialize:
     case no_actionlist:
+    case no_orderedactionlist:
     case no_definesideeffect:
     case no_dataset_alias:
         return GraphNeverAccess;               // Will never access any data values from anywhere

+ 158 - 59
ecl/hqlcpp/hqlresource.cpp

@@ -645,6 +645,7 @@ ResourceGraphInfo::ResourceGraphInfo(CResourceOptions * _options) : resources(_o
     mergedConditionSource = false;
     hasConditionSource = false;
     hasSequentialSource = false;
+    hasRootActivity = false;
     isDead = false;
     startedGeneratingResourced = false;
     inheritedExpandedDependencies = false;
@@ -730,7 +731,18 @@ bool ResourceGraphInfo::allocateResources(const CResources & value, const CResou
 }
 
 
-bool ResourceGraphInfo::containsActiveSinks()
+bool ResourceGraphInfo::canMergeActionAsSibling(bool sequential) const
+{
+    //sequential requires root actions in separate graphs
+    if (sequential && hasRootActivity)
+        return false;
+    //Dependent actions need to be in a separate graph if this flag is set.
+    if (options->actionLinkInNewGraph && !hasRootActivity)
+        return false;
+    return true;
+}
+
+bool ResourceGraphInfo::containsActiveSinks() const
 {
     ForEachItemIn(idx, sinks)
     {
@@ -741,6 +753,17 @@ bool ResourceGraphInfo::containsActiveSinks()
     return false;
 }
 
+bool ResourceGraphInfo::containsActionSink() const
+{
+    ForEachItemIn(idx, sinks)
+    {
+        IHqlExpression * sink = sinks.item(idx).sourceNode;
+        if (sink->isAction())
+            return true;
+    }
+    return false;
+}
+
 void ResourceGraphInfo::display()
 {
     StringBuffer s;
@@ -998,6 +1021,7 @@ ResourcerInfo::ResourcerInfo(IHqlExpression * _original, CResourceOptions * _opt
     gatheredDependencies = false;
     containsActivity = false;
     isActivity = false;
+    isRootActivity = false;
     transformed = NULL;
     conditionSourceCount = 0;
     pathToExpr = PathUnknown;
@@ -1382,6 +1406,11 @@ bool ResourcerInfo::spillSharesSplitter()
     return true;
 }
 
+void ResourcerInfo::setRootActivity()
+{
+    isRootActivity = true;
+}
+
 IHqlExpression * ResourcerInfo::createSpiller(IHqlExpression * transformed, bool reuseSplitter)
 {
     if (outputToUseForSpill)
@@ -1849,6 +1878,8 @@ EclResourcer::EclResourcer(IErrorReceiver * _errors, IConstWorkUnit * _wu, Clust
     options.createSpillAsDataset = _translatorOptions.optimizeSpillProject && (targetClusterType != HThorCluster);
     options.combineSiblings = _translatorOptions.combineSiblingGraphs && (targetClusterType != HThorCluster) && (targetClusterType != RoxieCluster);
     options.optimizeSharedInputs = _translatorOptions.optimizeSharedGraphInputs && options.combineSiblings;
+    options.actionLinkInNewGraph = _translatorOptions.actionLinkInNewGraph  || (targetClusterType == HThorCluster);
+    options.convertCompoundToExecuteWhen = false;
 }
 
 EclResourcer::~EclResourcer()               
@@ -2324,10 +2355,10 @@ protected:
             {
                 IHqlExpression * cond = expr->queryChild(0);
                 analyseExpr(cond);
-                if (expr->isDataset() || expr->isDatarow() || expr->isDictionary())
+                if (expr->isDataset() || expr->isDatarow() || expr->isDictionary() || expr->isAction())
                     conditionalDepth++;
                 doAnalyseChildren(expr, 1);
-                if (expr->isDataset() || expr->isDatarow() || expr->isDictionary())
+                if (expr->isDataset() || expr->isDatarow() || expr->isDictionary() || expr->isAction())
                     conditionalDepth--;
                 break;
             }
@@ -3061,12 +3092,17 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
                 thisGraph->hasSequentialSource = true;
         }
         info->graph.set(thisGraph);
+        if (info->isRootActivity)
+            thisGraph->hasRootActivity = true;
 
         switch (expr->getOperator())
         {
         case no_compound:
             //NB: First argument is forced into a separate graph
-            createInitialGraph(expr->queryChild(0), expr, NULL, UnconditionalLink, true);
+            if (options.convertCompoundToExecuteWhen)
+                createInitialGraph(expr->queryChild(0), expr, thisGraph, UnconditionalLink, true);
+            else
+                createInitialGraph(expr->queryChild(0), expr, NULL, UnconditionalLink, true);
             createInitialGraph(expr->queryChild(1), expr, thisGraph, UnconditionalLink, false);
             return;
         case no_executewhen:
@@ -3080,9 +3116,10 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
         case no_newkeyindex:
             return;
         case no_parallel:
+        case no_actionlist:
             {
                 ForEachChild(i, expr)
-                    createInitialGraph(expr->queryChild(i), expr, thisGraph, UnconditionalLink, true);
+                    createInitialGraph(expr->queryChild(i), expr, thisGraph, UnconditionalLink, options.actionLinkInNewGraph);
                 return;
             }
         case no_if:
@@ -3091,7 +3128,7 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
             //conditional nodes, the child branches are marked as conditional
             childLinkKind = UnconditionalLink;
             thisGraph->mergedConditionSource = true;
-            if (!options.noConditionalLinks || expr->isAction())
+            if (!options.noConditionalLinks || (expr->isAction() && options.actionLinkInNewGraph))
                 forceNewChildGraph = true;
             break;
         case no_filter:
@@ -3104,12 +3141,13 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
             break;
 //      case no_nonempty:
         case no_sequential:
+        case no_orderedactionlist:
             {
                 unsigned first = getFirstActivityArgument(expr);
                 unsigned last = first + getNumActivityArguments(expr);
-                createInitialGraph(expr->queryChild(first), expr, thisGraph, SequenceLink, true);
+                createInitialGraph(expr->queryChild(first), expr, thisGraph, SequenceLink, options.actionLinkInNewGraph);
                 for (unsigned idx = first+1; idx < last; idx++)
-                    createInitialGraph(expr->queryChild(idx), expr, thisGraph, SequenceLink, true);
+                    createInitialGraph(expr->queryChild(idx), expr, thisGraph, SequenceLink, options.actionLinkInNewGraph);
                 return;
             }
         case no_case:
@@ -3164,7 +3202,14 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
 void EclResourcer::createInitialGraphs(HqlExprArray & exprs)
 {
     ForEachItemIn(idx, exprs)
-        createInitialGraph(&exprs.item(idx), NULL, NULL, UnconditionalLink, false);
+    {
+        IHqlExpression * rootExpr = &exprs.item(idx);
+        ResourcerInfo * info = queryResourceInfo(rootExpr);
+        assertex(info->isActivity);
+        info->setRootActivity();
+
+        createInitialGraph(rootExpr, NULL, NULL, UnconditionalLink, false);
+    }
 }
 
 void EclResourcer::createInitialRemoteGraph(IHqlExpression * expr, IHqlExpression * owner, ResourceGraphInfo * ownerGraph, bool forceNewGraph)
@@ -3194,7 +3239,10 @@ void EclResourcer::createInitialRemoteGraph(IHqlExpression * expr, IHqlExpressio
         switch (expr->getOperator())
         {
         case no_compound:
-            createInitialRemoteGraph(expr->queryChild(0), expr, NULL, true);
+            if (options.convertCompoundToExecuteWhen)
+                createInitialRemoteGraph(expr->queryChild(0), expr, thisGraph, true);
+            else
+                createInitialRemoteGraph(expr->queryChild(0), expr, NULL, true);
             createInitialRemoteGraph(expr->queryChild(1), expr, thisGraph, false);
             return;
         case no_executewhen:
@@ -3316,6 +3364,7 @@ void EclResourcer::markAsUnconditional(IHqlExpression * expr, ResourceGraphInfo
         markChildDependentsAsUnconditional(info, condition);
         return;
     case no_sequential:
+    case no_orderedactionlist:
 //  case no_nonempty:
         if (!options.isChildQuery)
         {
@@ -4149,6 +4198,9 @@ mergeAgain:
                                 if (curLink.sinkNode != sourceResourceInfo->outputToUseForSpill)
                                     ok = false;
                             }
+                            if (sequential && source->containsActionSink())
+                                ok = false;
+
                             unsigned curSourceDepth = source->getDepth();
                             //MORE: Merging identical conditionals?
                             if (ok && queryMergeGraphLink(curLink) &&
@@ -4201,6 +4253,12 @@ void EclResourcer::mergeSiblings()
             ResourceGraphInfo & cur = graphs.item(idx);
             if ((cur.getDepth() == curDepth) && !cur.isDead)
             {
+                if (cur.containsActionSink())
+                {
+                    if (!cur.canMergeActionAsSibling(sequential))
+                        continue;
+                }
+
                 ForEachItemIn(idxSource, cur.sources)
                 {
                     ResourceGraphLink & curLink = cur.sources.item(idxSource);
@@ -4214,9 +4272,14 @@ void EclResourcer::mergeSiblings()
                     {
                         ResourceGraphLink & secondLink = source->sinks.item(iSink);
                         ResourceGraphInfo * sink = secondLink.sinkGraph;
+                        bool ok = false;
                         if (sink && (sink != &cur) && !sink->isDead && sourceNode->queryBody() == secondLink.sourceNode->queryBody())
                         {
-                            if (cur.mergeInSibling(*sink, *resourceLimit))
+                            ok = true;
+                            if (sequential && !sink->canMergeActionAsSibling(sequential))
+                                ok = false;
+
+                            if (ok && cur.mergeInSibling(*sink, *resourceLimit))
                             {
                                 //NB: Following cannot remove sources below the current index.
                                 replaceGraphReferences(sink, &cur);
@@ -4529,7 +4592,15 @@ IHqlExpression * EclResourcer::doCreateResourced(IHqlExpression * expr, Resource
     case no_keyed:
         return LINK(expr);
     case no_compound:
-        transformed.setown(createResourced(expr->queryChild(1), ownerGraph, expandInParent, false));
+        if (options.convertCompoundToExecuteWhen)
+        {
+            //NB: Arguments to no_executewhen are the reverse of no_compound.
+            args.append(*createResourced(expr->queryChild(1), ownerGraph, expandInParent, false));
+            args.append(*createResourced(expr->queryChild(0), ownerGraph, expandInParent, false));
+            transformed.setown(createDataset(no_executewhen, args));
+        }
+        else
+            transformed.setown(createResourced(expr->queryChild(1), ownerGraph, expandInParent, false));
         break;
     case no_executewhen:
         {
@@ -4731,8 +4802,11 @@ IHqlExpression * EclResourcer::createResourced(IHqlExpression * expr, ResourceGr
 
         if (isShared)
         {
-            source = createDatasetF(no_split, source, createAttribute(balancedAtom), createUniqueId(), NULL);
-            ownerGraph->addSharedInput(expr->queryBody(), source);
+            if (!source->isAction())
+            {
+                source = createDatasetF(no_split, source, createAttribute(balancedAtom), createUniqueId(), NULL);
+                ownerGraph->addSharedInput(expr->queryBody(), source);
+            }
         }
 
         return source;
@@ -4949,8 +5023,48 @@ void EclResourcer::trace()
 
 //---------------------------------------------------------------------------
 
-void EclResourcer::resourceGraph(HqlExprArray & exprs, HqlExprArray & transformed)
+static void expandLists(node_operator op, HqlExprArray & args, IHqlExpression * expr);
+static void expandChildren(node_operator op, HqlExprArray & args, IHqlExpression * expr)
 {
+    ForEachChild(idx, expr)
+        expandLists(op, args, expr->queryChild(idx));
+}
+
+static void expandLists(node_operator op, HqlExprArray & args, IHqlExpression * expr)
+{
+    switch (expr->getOperator())
+    {
+    case no_sequential:
+    case no_orderedactionlist:
+        if (op != no_parallel)
+            expandChildren(no_sequential, args, expr);
+        else
+            args.append(*LINK(expr));
+        break;
+    case no_comma:
+    case no_compound:
+    case no_actionlist:
+    case no_parallel:
+        if (op != no_sequential)
+            expandChildren(no_parallel, args, expr);
+        else
+            args.append(*LINK(expr));
+        break;
+    case no_null:
+        break;
+    default:
+        args.append(*LINK(expr));
+        break;
+    }
+}
+
+
+void EclResourcer::resourceGraph(IHqlExpression * expr, HqlExprArray & transformed)
+{
+    HqlExprArray exprs;
+    node_operator expandOp = options.isChildQuery ? no_any : no_parallel;
+    expandLists(expandOp, exprs, expr);
+
     //NB: This only resources a single level of queries.  SubQueries should be resourced in a separate
     //pass so that commonality between different activities/subgraphs isn't introduced/messed up.
     findSplitPoints(exprs);
@@ -4977,8 +5091,11 @@ void EclResourcer::resourceGraph(HqlExprArray & exprs, HqlExprArray & transforme
 }
 
 
-void EclResourcer::resourceRemoteGraph(HqlExprArray & exprs, HqlExprArray & transformed)
+void EclResourcer::resourceRemoteGraph(IHqlExpression * expr, HqlExprArray & transformed)
 {
+    HqlExprArray exprs;
+    expandLists(no_any, exprs, expr);
+
     //NB: This only resources a single level of queries.  SubQueries should be resourced in a separate
     //pass so that commonality between different activities/subgraphs isn't introduced/messed up.
     findSplitPoints(exprs);
@@ -4998,27 +5115,6 @@ void EclResourcer::resourceRemoteGraph(HqlExprArray & exprs, HqlExprArray & tran
 
 //---------------------------------------------------------------------------
 
-void expandLists(HqlExprArray & args, IHqlExpression * expr)
-{
-    switch (expr->getOperator())
-    {
-    case no_comma:
-    case no_compound:
-    case no_parallel:       
-    case no_actionlist:
-        // for the moment, expand root parallel nodes, it generates much better code.
-        // I should really come up with a better way of implementing sequential/parallel.
-        {
-            ForEachChild(idx, expr)
-                expandLists(args, expr->queryChild(idx));
-            break;
-        }
-    default:
-        args.append(*LINK(expr));
-        break;
-    }
-}
-
 IHqlExpression * resourceThorGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr)
 {
     HqlExprArray transformed;
@@ -5027,9 +5123,7 @@ IHqlExpression * resourceThorGraph(HqlCppTranslator & translator, IHqlExpression
         if (graphIdExpr)
             resourcer.setNewChildQuery(graphIdExpr, 0);
 
-        HqlExprArray exprs;
-        expandLists(exprs, expr);
-        resourcer.resourceGraph(exprs, transformed);
+        resourcer.resourceGraph(expr, transformed);
     }
     hoistNestedCompound(translator, transformed);
     return createActionList(transformed);
@@ -5038,45 +5132,53 @@ IHqlExpression * resourceThorGraph(HqlCppTranslator & translator, IHqlExpression
 
 static IHqlExpression * doResourceGraph(HqlCppTranslator & translator, HqlExprCopyArray * activeRows, IHqlExpression * expr, 
                                         ClusterType targetClusterType, unsigned clusterSize,
-                                        IHqlExpression * graphIdExpr, unsigned * numResults, bool isChild, bool useGraphResults, bool sequential)
+                                        IHqlExpression * graphIdExpr, unsigned numResults, bool isChild, bool useGraphResults)
 {
     HqlExprArray transformed;
+    unsigned totalResults;
     {
         EclResourcer resourcer(translator.queryErrors(), translator.wu(), targetClusterType, clusterSize, translator.queryOptions());
         if (isChild)
             resourcer.setChildQuery(true);
-        resourcer.setNewChildQuery(graphIdExpr, *numResults);
+        resourcer.setNewChildQuery(graphIdExpr, numResults);
         resourcer.setUseGraphResults(useGraphResults);
-        resourcer.setSequential(sequential);
+
+        if (isSequentialActionList(expr))
+            resourcer.setSequential(true);
 
         if (activeRows)
             resourcer.tagActiveCursors(*activeRows);
 
-        HqlExprArray exprs;
-        expandLists(exprs, expr);
-
-        resourcer.resourceGraph(exprs, transformed);
-        *numResults = resourcer.numGraphResults();
+        resourcer.resourceGraph(expr, transformed);
+        totalResults = resourcer.numGraphResults();
     }
+
     hoistNestedCompound(translator, transformed);
-    return createActionList(transformed);
+
+    if (totalResults == 0)
+        totalResults = 1;
+    transformed.append(*createAttribute(numResultsAtom, getSizetConstant(totalResults)));
+    transformed.append(*LINK(graphIdExpr));
+    if (isSequentialActionList(expr))
+        transformed.append(*createAttribute(sequentialAtom));
+    return createValue(no_subgraph, makeVoidType(), transformed);
 }
 
 
-IHqlExpression * resourceLibraryGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr, unsigned * numResults)
+IHqlExpression * resourceLibraryGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr, unsigned numResults)
 {
-    return doResourceGraph(translator, NULL, expr, targetClusterType, clusterSize, graphIdExpr, numResults, false, true, false);       //?? what value for isChild (e.g., thor library call).  Need to gen twice?
+    return doResourceGraph(translator, NULL, expr, targetClusterType, clusterSize, graphIdExpr, numResults, false, true);       //?? what value for isChild (e.g., thor library call).  Need to gen twice?
 }
 
 
-IHqlExpression * resourceNewChildGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned * numResults, bool sequential)
+IHqlExpression * resourceNewChildGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned numResults)
 {
-    return doResourceGraph(translator, &activeRows, expr, targetClusterType, 0, graphIdExpr, numResults, true, true, sequential);
+    return doResourceGraph(translator, &activeRows, expr, targetClusterType, 0, graphIdExpr, numResults, true, true);
 }
 
-IHqlExpression * resourceLoopGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned * numResults, bool insideChildQuery)
+IHqlExpression * resourceLoopGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned numResults, bool insideChildQuery)
 {
-    return doResourceGraph(translator, &activeRows, expr, targetClusterType, 0, graphIdExpr, numResults, insideChildQuery, true, false);
+    return doResourceGraph(translator, &activeRows, expr, targetClusterType, 0, graphIdExpr, numResults, insideChildQuery, true);
 }
 
 IHqlExpression * resourceRemoteGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize)
@@ -5085,10 +5187,7 @@ IHqlExpression * resourceRemoteGraph(HqlCppTranslator & translator, IHqlExpressi
     {
         EclResourcer resourcer(translator.queryErrors(), translator.wu(), targetClusterType, clusterSize, translator.queryOptions());
 
-        HqlExprArray exprs;
-        expandLists(exprs, expr);
-
-        resourcer.resourceRemoteGraph(exprs, transformed);
+        resourcer.resourceRemoteGraph(expr, transformed);
     }
     hoistNestedCompound(translator, transformed);
     return createActionList(transformed);

+ 3 - 3
ecl/hqlcpp/hqlresource.hpp

@@ -22,9 +22,9 @@
 #include "hqlcpp.ipp"
 
 IHqlExpression * resourceThorGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr);
-IHqlExpression * resourceLibraryGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr, unsigned * numResults);
-IHqlExpression * resourceLoopGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned * numResults, bool insideChildQuery);
-IHqlExpression * resourceNewChildGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned * numResults, bool sequential);
+IHqlExpression * resourceLibraryGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize, IHqlExpression * graphIdExpr, unsigned numResults);
+IHqlExpression * resourceLoopGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned numResults, bool insideChildQuery);
+IHqlExpression * resourceNewChildGraph(HqlCppTranslator & translator, HqlExprCopyArray & activeRows, IHqlExpression * expr, ClusterType targetClusterType, IHqlExpression * graphIdExpr, unsigned numResults);
 IHqlExpression * resourceRemoteGraph(HqlCppTranslator & translator, IHqlExpression * expr, ClusterType targetClusterType, unsigned clusterSize);
 
 IHqlExpression * convertSpillsToActivities(IHqlExpression * expr);

+ 12 - 7
ecl/hqlcpp/hqlresource.ipp

@@ -36,7 +36,7 @@ enum ResourceType {
 class CResourceOptions
 {
 public:
-    CResourceOptions() { state.updateSequence = 0; }
+    CResourceOptions() { memset(this, 0, sizeof(*this)); state.updateSequence = 0; }
 
     IHqlExpression * createSpillName(bool isGraphResult);
     void noteGraphsChanged() { state.updateSequence++; }
@@ -63,6 +63,8 @@ public:
     bool     createSpillAsDataset;
     bool     optimizeSharedInputs;
     bool     combineSiblings;
+    bool     actionLinkInNewGraph;
+    bool     convertCompoundToExecuteWhen;
 
     IHqlExpression * graphIdExpr;
     unsigned nextResult;
@@ -153,7 +155,9 @@ public:
 
     bool addCondition(IHqlExpression * condition);
     bool allocateResources(const CResources & value, const CResources & limit);
-    bool containsActiveSinks();
+    bool canMergeActionAsSibling(bool sequential) const;
+    bool containsActiveSinks() const;
+    bool containsActionSink() const;
     unsigned getDepth();
     void getMergeFailReason(StringBuffer & reasonText, ResourceGraphInfo * otherGraph, const CResources & limit);
     bool hasSameConditions(ResourceGraphInfo & other);
@@ -190,6 +194,7 @@ public:
     bool mergedConditionSource:1;
     bool hasConditionSource:1;
     bool hasSequentialSource:1;
+    bool hasRootActivity:1;
     bool isDead:1;
     bool startedGeneratingResourced:1;
     bool inheritedExpandedDependencies:1;
@@ -280,8 +285,7 @@ public:
     {
         return isActivity || containsActivity;
     }
-
-
+    void setRootActivity();
 
 protected:
     bool spillSharesSplitter();
@@ -317,8 +321,10 @@ public:
     unsigned numExternalUses;
     unsigned conditionSourceCount;
     unsigned currentSource;
+    byte pathToExpr;
     bool containsActivity;
     bool isActivity;
+    bool isRootActivity;
     bool gatheredDependencies;
     bool isSpillPoint;
     bool balanced;
@@ -326,7 +332,6 @@ public:
     bool linkedFromChild;
     bool forceHoist;
     bool neverSplit;
-    byte pathToExpr;
     bool isConditionalFilter;
     bool projectResult;
     bool visited;
@@ -347,8 +352,8 @@ public:
     EclResourcer(IErrorReceiver * _errors, IConstWorkUnit * _wu, ClusterType _targetClusterType, unsigned _clusterSize, const HqlCppOptions & _translatorOptions);
     ~EclResourcer();
 
-    void resourceGraph(HqlExprArray & exprs, HqlExprArray & transformed);
-    void resourceRemoteGraph(HqlExprArray & exprs, HqlExprArray & transformed);
+    void resourceGraph(IHqlExpression * expr, HqlExprArray & transformed);
+    void resourceRemoteGraph(IHqlExpression * expr, HqlExprArray & transformed);
     void setChildQuery(bool value);
     void setNewChildQuery(IHqlExpression * graphIdExpr, unsigned numResults);
     void setSequential(bool _sequential) { sequential = _sequential; }

+ 11 - 2
ecl/hqlcpp/hqlttcpp.cpp

@@ -567,6 +567,7 @@ IHqlExpression * NewThorStoredReplacer::createTransformed(IHqlExpression * expr)
             return transform(expr->queryChild(0));
         break;
     case no_actionlist:
+    case no_orderedactionlist:
         {
             HqlExprArray actions;
             ForEachChild(i, expr)
@@ -691,6 +692,7 @@ IHqlExpression * HqlThorBoundaryTransformer::createTransformed(IHqlExpression *
     switch (op)
     {
     case no_actionlist:
+    case no_orderedactionlist:
         {
             HqlExprArray nonThor, args;
             expr->unwindList(args, op);
@@ -849,6 +851,7 @@ YesNoOption HqlThorBoundaryTransformer::calcNormalizeThor(IHqlExpression * expr)
             return OptionNo;
         // fallthrough
     case no_actionlist:
+    case no_orderedactionlist:
     case no_parallel:
         {
             YesNoOption option = OptionUnknown;
@@ -857,7 +860,8 @@ YesNoOption HqlThorBoundaryTransformer::calcNormalizeThor(IHqlExpression * expr)
                 YesNoOption childOption = normalizeThor(expr->queryChild(idx));
                 if (childOption == OptionNo)
                     return OptionNo;
-                option = combine(option, childOption, (op == no_sequential));       // can reorder parallel - so intersection is better
+                bool fixedOrder = (op == no_sequential) || (op == no_orderedactionlist);
+                option = combine(option, childOption, fixedOrder);       // can reorder parallel - so intersection is better
             }
             return option;
         }
@@ -1301,6 +1305,7 @@ IHqlExpression * SequenceNumberAllocator::doTransformRootExpr(IHqlExpression * e
     case no_parallel:
     case no_sequential:
     case no_actionlist:
+    case no_orderedactionlist:
         {
             HqlExprArray args;
             ForEachChild(idx, expr)
@@ -6473,6 +6478,7 @@ IHqlExpression * WorkflowTransformer::transformRootAction(IHqlExpression * expr)
     case no_parallel:
         return createParallelWorkflow(expr);
     case no_sequential:
+    case no_orderedactionlist:
         return createSequentialWorkflow(expr);
     case no_actionlist:
         return createCompoundWorkflow(expr);
@@ -7002,6 +7008,7 @@ static void mergeThorGraphs(HqlExprArray & exprs, bool resourceConditionalAction
             }
             //fall through
         case no_actionlist:
+        case no_orderedactionlist:
             {
                 HqlExprArray args;
                 cur->unwindList(args, op);
@@ -7417,6 +7424,7 @@ protected:
         case no_sequential:
         case no_compound:
         case no_actionlist:
+        case no_orderedactionlist:
             NewHqlTransformer::analyseExpr(expr);
             break;
         case no_output:
@@ -10779,7 +10787,7 @@ IHqlExpression * HqlTreeNormalizer::transformActionList(IHqlExpression * expr)
                 args.append(*transformed.getClear());
         }
     }
-    return createActionList(args);
+    return expr->clone(args);
 }
 
 
@@ -12079,6 +12087,7 @@ IHqlExpression * HqlTreeNormalizer::createTransformedBody(IHqlExpression * expr)
         }
         break;
     case no_actionlist:
+    case no_orderedactionlist:
         return transformActionList(expr);
     case no_forcelocal:
     case no_forcenolocal:

+ 35 - 0
testing/ecl/action1.ecl

@@ -0,0 +1,35 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+r := { string text; };
+
+msg(string x) := DATASET([x], {r});
+
+o1 := OUTPUT(msg('One'),,NAMED('msgs'),EXTEND);
+o2 := OUTPUT(msg('Two'),,NAMED('msgs'),EXTEND);
+o3 := OUTPUT(msg('Three'),,NAMED('msgs'),EXTEND);
+
+msgs := DATASET(WORKUNIT('msgs'), r);
+
+//Should have the same values for the two output(counts), and should be 3
+t1 := [o1,o2,o3,OUTPUT(count(msgs)),o3,o2,o1,OUTPUT(count(msgs))];
+
+t2 := [o3,o2,o1,OUTPUT(count(msgs)),o1,o2,o3,OUTPUT(count(msgs))];
+
+boolean updown := true : stored('updown');
+
+IF(updown, t1, t2);

+ 35 - 0
testing/ecl/action2.ecl

@@ -0,0 +1,35 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+r := { string text; };
+
+msg(string x) := DATASET([x], {r});
+
+o1 := OUTPUT(msg('One'),,NAMED('msgs'),EXTEND);
+o2 := OUTPUT(msg('Two'),,NAMED('msgs'),EXTEND);
+o3 := OUTPUT(msg('Three'),,NAMED('msgs'),EXTEND);
+
+msgs := DATASET(WORKUNIT('msgs'), r);
+
+//Should have the same values for the two output(counts), and should be 3
+t1 := SEQUENTIAL(o1,o2,o3,OUTPUT(count(msgs)),o3,o2,o1,OUTPUT(count(msgs)));
+
+t2 := SEQUENTIAL(o3,o2,o1,OUTPUT(count(msgs)),o1,o2,o3,OUTPUT(count(msgs)));
+
+boolean updown := true : stored('updown');
+
+IF(updown, t1, t2);

+ 35 - 0
testing/ecl/action3.ecl

@@ -0,0 +1,35 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+r := { string text; };
+
+msg(string x) := DATASET([x], {r});
+
+o1 := OUTPUT(msg('One'),,NAMED('msgs'),EXTEND);
+o2 := OUTPUT(msg('Two'),,NAMED('msgs'),EXTEND);
+o3 := OUTPUT(msg('Three'),,NAMED('msgs'),EXTEND);
+
+msgs := DATASET(WORKUNIT('msgs'), r);
+
+//Should have the same values for the two output(counts), and should be ??
+t1 := PARALLEL(o1,o2,o3,OUTPUT(count(msgs)),o3,o2,o1,OUTPUT(count(msgs)));
+
+t2 := PARALLEL(o3,o2,o1,OUTPUT(count(msgs)),o1,o2,o3,OUTPUT(count(msgs)));
+
+boolean updown := true : stored('updown');
+
+IF(updown, t1, t2);

+ 37 - 0
testing/ecl/action4.ecl

@@ -0,0 +1,37 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+r := { string text; };
+
+msg(string x) := DATASET([x], {r});
+
+o1 := OUTPUT(msg('One'),,NAMED('msgs'),EXTEND);
+o2 := OUTPUT(msg('Two'),,NAMED('msgs'),EXTEND);
+o3 := OUTPUT(msg('Three'),,NAMED('msgs'),EXTEND);
+
+msgs := DATASET(WORKUNIT('msgs'), r);
+
+//Should have the same values for the two output(counts), and should be 3
+ta := [o1,o2,o3,OUTPUT(count(msgs))];
+tb := [o3,o2,o1,OUTPUT(count(msgs))];
+
+t1 := [ta, tb];
+t2 := [tb,ta];
+
+boolean updown := true : stored('updown');
+
+IF(updown, t1, t2);

+ 37 - 0
testing/ecl/action5.ecl

@@ -0,0 +1,37 @@
+/*##############################################################################
+
+    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.
+############################################################################## */
+
+r := { string text; };
+
+msg(string x) := DATASET([x], {r});
+
+o1 := OUTPUT(msg('One'),,NAMED('msgs'),EXTEND);
+o2 := OUTPUT(msg('Two'),,NAMED('msgs'),EXTEND);
+o3 := OUTPUT(msg('Three'),,NAMED('msgs'),EXTEND);
+
+msgs := DATASET(WORKUNIT('msgs'), r);
+
+//Should have the same values for the two output(counts), and should be 3
+ta := ORDERED(o1,o2,o3,OUTPUT(count(msgs)));
+tb := ORDERED(o3,o2,o1,OUTPUT(count(msgs)));
+
+t1 := ORDERED(ta, tb);
+t2 := ORDERED(tb,ta);
+
+boolean updown := true : stored('updown');
+
+IF(updown, t1, t2);

+ 14 - 0
testing/ecl/key/action1.xml

@@ -0,0 +1,14 @@
+<Dataset name='msgs'>
+ <Row><text>One</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>One</text></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>3</Result_3></Row>
+</Dataset>

+ 14 - 0
testing/ecl/key/action2.xml

@@ -0,0 +1,14 @@
+<Dataset name='msgs'>
+ <Row><text>One</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>One</text></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>6</Result_3></Row>
+</Dataset>

+ 14 - 0
testing/ecl/key/action3.xml

@@ -0,0 +1,14 @@
+<Dataset name='msgs'>
+ <Row><text>One</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>One</text></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>3</Result_3></Row>
+</Dataset>

+ 14 - 0
testing/ecl/key/action4.xml

@@ -0,0 +1,14 @@
+<Dataset name='msgs'>
+ <Row><text>One</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>One</text></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>3</Result_3></Row>
+</Dataset>

+ 14 - 0
testing/ecl/key/action5.xml

@@ -0,0 +1,14 @@
+<Dataset name='msgs'>
+ <Row><text>One</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Three</text></Row>
+ <Row><text>Two</text></Row>
+ <Row><text>One</text></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>3</Result_3></Row>
+</Dataset>