浏览代码

gh-3294 Implementation of CHOOSE(expr, ds, ds, ds)

* The internal representation is better than case/map for processing.

* Provide a syntax for the ECL user for consitency with expressions.

* Translate CASE() dataset operator into no_chooseds - produces better
  code.

* Easier to translate conditional guards into this form (and can be used
  for more types of conditions)

- Fix a pre-existing bug on converting a CASE to an IF where the types of the
  arguments weren't set correctly.

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 13 年之前
父节点
当前提交
44ea0d9241

+ 3 - 3
ecl/eclagent/eclgraph.cpp

@@ -664,9 +664,9 @@ bool EclGraphElement::prepare(IAgentContext & agent, const byte * parentExtract,
                     throw makeWrappedException(e);
                 }
                 whichBranch = ((IHThorCaseArg *)helper.get())->getBranch();
-                if (branches.isItem(whichBranch))
-                    return branches.item(whichBranch).prepare(agent, parentExtract, checkDependencies);
-                return true;
+                if (whichBranch >= branches.ordinality())
+                    whichBranch = branches.ordinality()-1;
+                return branches.item(whichBranch).prepare(agent, parentExtract, checkDependencies);
             }
         }
 

+ 23 - 13
ecl/hql/hqlattr.cpp

@@ -398,6 +398,7 @@ unsigned getOperatorMetaFlags(node_operator op)
     case no_normalizegroup:
     case no_owned_ds:
     case no_dataset_alias:
+    case no_chooseds:
 
 //Multiple different kinds of values
     case no_select:
@@ -615,7 +616,7 @@ unsigned getOperatorMetaFlags(node_operator op)
     case no_dataset_from_transform:
 
     case no_unused6:
-    case no_unused13: case no_unused14: case no_unused15: case no_unused18: case no_unused19:
+    case no_unused13: case no_unused14: case no_unused15: case no_unused19:
     case no_unused20: case no_unused21: case no_unused22: case no_unused23: case no_unused24: 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:
@@ -1563,6 +1564,7 @@ bool isLocalActivity(IHqlExpression * expr)
     case no_distribute:
     case no_keyeddistribute:
     case no_if:
+    case no_chooseds:
         return false;
     case no_forcelocal:
     case no_combinegroup:
@@ -1692,6 +1694,7 @@ bool isGroupedActivity(IHqlExpression * expr)
     case no_combine:
     case no_combinegroup:
     case no_if:
+    case no_chooseds:
     case no_case:
     case no_map:
     case no_loop:
@@ -2349,6 +2352,22 @@ void retrieveRowInformation(HqlRowCountInfo & info, IHqlExpression * expr)
     info.extract(attr);
 }
 
+static void calcIntersectingRowInformation(HqlRowCountInfo & info, IHqlExpression * expr, unsigned firstDs)
+{
+   retrieveRowInformation(info, expr->queryChild(firstDs));
+
+    ForEachChildFrom(i, expr, firstDs+1)
+    {
+        IHqlExpression * cur = expr->queryChild(i);
+        if (!cur->isAttribute())
+        {
+            HqlRowCountInfo nextInfo;
+            retrieveRowInformation(nextInfo, cur);
+            info.combineBoth(nextInfo);
+        }
+    }
+}
+
 //MORE: This would benefit from knowing if the target is hthor/roxie (or a thoir child query) so it could tell if local means
 //anything.  The best solution is to annotate the graph with _global_ for thor, or _single_ for the others.  One day....
 IHqlExpression * calcRowInformation(IHqlExpression * expr)
@@ -2669,23 +2688,14 @@ IHqlExpression * calcRowInformation(IHqlExpression * expr)
             }
             break;
         }
+    case no_chooseds:
     case no_regroup:
     case no_combinegroup:
     case no_addfiles:
     case no_merge:
         {
-            retrieveRowInformation(info, ds);
-
-            ForEachChildFrom(i, expr, 1)
-            {
-                IHqlExpression * cur = expr->queryChild(i);
-                if (!cur->isAttribute())
-                {
-                    HqlRowCountInfo nextInfo;
-                    retrieveRowInformation(nextInfo, cur);
-                    info.combineBoth(nextInfo);
-                }
-            }
+            unsigned firstDataset = getFirstActivityArgument(expr);
+            calcIntersectingRowInformation(info, expr, firstDataset);
             break;
         }
     case no_choosen:

+ 15 - 2
ecl/hql/hqlexpr.cpp

@@ -1447,9 +1447,10 @@ const char *getOpString(node_operator op)
     case no_dataset_alias: return "TABLE";
     case no_childquery: return "no_childquery";
     case no_inlinedictionary: case no_userdictionary: case no_newuserdictionary: return "DICTIONARY";
+    case no_chooseds: return "CHOOSE";
 
     case no_unused6:
-    case no_unused13: case no_unused14: case no_unused15: case no_unused18: case no_unused19:
+    case no_unused13: case no_unused14: case no_unused15: case no_unused19:
     case no_unused20: case no_unused21: case no_unused22: case no_unused23: case no_unused24: 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:
@@ -1953,6 +1954,8 @@ childDatasetType getChildDatasetType(IHqlExpression * expr)
         if (expr->queryChild(1)->isDataset())
             return childdataset_leftright;
         return childdataset_left;
+    case no_chooseds:
+        return childdataset_many_noscope;
     case no_merge:
     case no_regroup:
     case no_cogroup:
@@ -2026,6 +2029,7 @@ inline unsigned doGetNumChildTables(IHqlExpression * dataset)
     case no_datasetlist:
     case no_nonempty:
     case no_cogroup:
+    case no_chooseds:
         {
             unsigned ret = 0;
             ForEachChild(idx, dataset)
@@ -2459,6 +2463,7 @@ bool definesColumnList(IHqlExpression * dataset)
     case no_if:
     case no_map:
     case no_case:
+    case no_chooseds:
     case no_colon:
     case no_globalscope:
     case no_nothor:
@@ -5937,6 +5942,8 @@ void CHqlDataset::cacheParent()
             }
             break;
         }
+    case no_addfiles:
+    case no_chooseds:
     default:
         if (getNumChildTables(this) == 1)
         {
@@ -10777,6 +10784,9 @@ IHqlExpression *createDataset(node_operator op, HqlExprArray & parms)
     case no_translated:
     case no_rows:
         break;
+    case no_chooseds:
+        datasetType = parms.item(1).queryType();
+        break;
     case no_mergejoin:
     case no_nwayjoin:
     case no_nwaymerge:
@@ -11422,6 +11432,7 @@ IHqlExpression *createDataset(node_operator op, HqlExprArray & parms)
     case no_addfiles:
     case no_regroup:
     case no_cogroup:
+    case no_chooseds:
         {
             // Note Concatenation destroys sort order 
             // If all the source files have the same distribution then preserve it, else just mark as distributed...
@@ -11429,7 +11440,8 @@ IHqlExpression *createDataset(node_operator op, HqlExprArray & parms)
             bool allGrouped = isGrouped(datasetType);
             bool sameDistribution = true;
             bool allInputsIdentical = true;
-            for (unsigned i=1; i < max; i++)
+            unsigned firstDataset = (op == no_chooseds) ? 1 : 0;
+            for (unsigned i=firstDataset+1; i < max; i++)
             {
                 IHqlExpression & cur = parms.item(i);
                 if (!cur.isAttribute())
@@ -13356,6 +13368,7 @@ static IHqlExpression * walkInstantEclTransformations(IHqlExpression * expr, uns
     case no_case:
     case no_map:
     case no_colon:
+    case no_chooseds:
         {
             HqlExprArray args;
             ForEachChild(i, expr)

+ 2 - 1
ecl/hql/hqlexpr.hpp

@@ -346,7 +346,7 @@ enum _node_operator {
         no_return_stmt,
         no_update,    
         no_shuffle,
-    no_unused18,
+        no_chooseds,
         no_alias,
     no_unused19,
     no_unused20,
@@ -1334,6 +1334,7 @@ extern HQL_API IHqlExpression * createConstantOne();
 extern HQL_API IHqlExpression * createLocalAttribute();
 extern HQL_API bool isNullExpr(IHqlExpression * expr, ITypeInfo * type);
 inline bool isNull(IHqlExpression * expr)       { return expr->getOperator() == no_null; }
+inline bool isNullAction(IHqlExpression * expr) { return isNull(expr) && expr->isAction(); }
 inline bool isFail(IHqlExpression * expr)       { return expr->getOperator() == no_fail; }
 
 extern HQL_API IHqlExpression * createDelayedReference(node_operator op, IHqlExpression * moduleMarker, IHqlExpression * attr, bool ignoreBase);

+ 7 - 3
ecl/hql/hqlfold.cpp

@@ -2110,6 +2110,7 @@ IHqlExpression * foldConstantOperator(IHqlExpression * expr, unsigned foldOption
             break;
         }
     case no_choose:
+    case no_chooseds:
         {
             IHqlExpression * child = expr->queryChild(0);
             IValue * constValue = child->queryValue();
@@ -2602,9 +2603,11 @@ IHqlExpression * foldConstantOperator(IHqlExpression * expr, unsigned foldOption
 
             if (numCases == 1)
             {
-                IHqlExpression * child = expr->queryChild(1);
-                IHqlExpression * newEqual = createBoolExpr(no_eq, LINK(leftExpr), LINK(child->queryChild(0)));
-                return createIf(newEqual, LINK(child->queryChild(1)), LINK(expr->queryChild(2)));
+                IHqlExpression * mapto = expr->queryChild(1);
+                IHqlExpression * key = mapto->queryChild(0);
+                OwnedITypeInfo type = getPromotedCompareType(leftExpr->queryType(), key->queryType());
+                IHqlExpression * newEqual = createBoolExpr(no_eq, ensureExprType(leftExpr, type), ensureExprType(key, type));
+                return createIf(newEqual, LINK(mapto->queryChild(1)), LINK(expr->queryChild(2)));
             }
             break;
         }
@@ -5627,6 +5630,7 @@ HqlConstantPercolator * CExprFolderTransformer::gatherConstants(IHqlExpression *
     case no_map:
     case no_merge:
     case no_cogroup:
+    case no_chooseds:
         {
             unsigned from = getFirstActivityArgument(expr);
             unsigned max = from + getNumActivityArguments(expr);

+ 30 - 0
ecl/hql/hqlgram.y

@@ -8593,6 +8593,36 @@ simpleDataSet
                             $3.release();
                             $$.setExpr($6.getExpr(), $1);
                         }
+    | CHOOSE '(' expression ',' mergeDataSetList ')'
+                        {
+                            parser->normalizeExpression($3, type_int, false);
+                            OwnedHqlExpr values = $5.getExpr();
+                            HqlExprArray unorderedArgs;
+                            values->unwindList(unorderedArgs, no_comma);
+
+                            HqlExprArray args;
+                            reorderAttributesToEnd(args, unorderedArgs);
+
+                            IHqlExpression * compareDs = NULL;
+                            ForEachItemIn(idx, args)
+                            {
+                                IHqlExpression * cur = &args.item(idx);
+                                if (cur->queryRecord())
+                                {
+                                    if (compareDs)
+                                    {
+                                        if (isGrouped(cur) != isGrouped(compareDs))
+                                            parser->reportError(ERR_GROUPING_MISMATCH, $1, "Branches of the condition have different grouping");
+                                        parser->checkRecordTypes(cur, compareDs, $5);
+                                    }
+                                    else
+                                        compareDs = cur;
+                                }
+                            }
+
+                            args.add(*$3.getExpr(), 0);
+                            $$.setExpr(createDataset(no_chooseds, args), $1);
+                        }
     | PARSE '(' startTopLeftSeqFilter ',' expression ',' startRootPattern ',' recordDef endRootPattern endTopLeftFilter doParseFlags ')' endSelectorSequence
                         {
                             parser->normalizeExpression($5, type_stringorunicode, false);

+ 1 - 1
ecl/hql/hqlgram2.cpp

@@ -10330,7 +10330,7 @@ void HqlGram::simplifyExpected(int *expected)
                        GROUP, GROUPED, KEYED, UNGROUP, JOIN, PULL, ROLLUP, ITERATE, PROJECT, NORMALIZE, PIPE, DENORMALIZE, CASE, MAP, 
                        HTTPCALL, SOAPCALL, LIMIT, PARSE, FAIL, MERGE, PRELOAD, ROW, TOPN, ALIAS, LOCAL, NOFOLD, NOHOIST, NOTHOR, IF, GLOBAL, __COMMON__, __COMPOUND__, TOK_ASSERT, _EMPTY_,
                        COMBINE, ROWS, REGROUP, XMLPROJECT, SKIP, LOOP, CLUSTER, NOLOCAL, REMOTE, PROCESS, ALLNODES, THISNODE, GRAPH, MERGEJOIN, STEPPED, NONEMPTY, HAVING,
-                       TOK_CATCH, '@', SECTION, WHEN, IFF, COGROUP, HINT, INDEX, PARTITION, AGGREGATE, SHUFFLE, TOK_ERROR, 0);
+                       TOK_CATCH, '@', SECTION, WHEN, IFF, COGROUP, HINT, INDEX, PARTITION, AGGREGATE, SHUFFLE, TOK_ERROR, CHOOSE, 0);
     simplify(expected, EXP, ABS, SIN, COS, TAN, SINH, COSH, TANH, ACOS, ASIN, ATAN, ATAN2, 
                        COUNT, CHOOSE, MAP, CASE, IF, HASH, HASH32, HASH64, HASHMD5, CRC, LN, TOK_LOG, POWER, RANDOM, ROUND, ROUNDUP, SQRT, 
                        TRUNCATE, LENGTH, TRIM, INTFORMAT, REALFORMAT, ASSTRING, TRANSFER, MAX, MIN, EVALUATE, SUM,

+ 2 - 1
ecl/hql/hqlir.cpp

@@ -819,9 +819,10 @@ static const char * getOperatorText(node_operator op)
     DUMP_CASE(no,countdict);
     DUMP_CASE(no,userdictionary);
     DUMP_CASE(no,newuserdictionary);
+    DUMP_CASE(no,chooseds);
 
     case no_unused6:
-    case no_unused13: case no_unused14: case no_unused15: case no_unused18: case no_unused19:
+    case no_unused13: case no_unused14: case no_unused15: case no_unused19:
     case no_unused20: case no_unused21: case no_unused22: case no_unused23: case no_unused24: 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:

+ 9 - 1
ecl/hql/hqlopt.cpp

@@ -356,7 +356,7 @@ IHqlExpression * CTreeOptimizer::swapIntoAddFiles(IHqlExpression * expr, bool fo
     ForEachChild(idx, child)
     {
         IHqlExpression * in = child->queryChild(idx);
-        if (in->isAttribute())
+        if (!in->isDataset() && !in->isDatarow())
         {
             replacedArgs.append(*LINK(in));
             transformedArgs.append(*LINK(in));
@@ -2065,6 +2065,7 @@ IHqlExpression * CTreeOptimizer::queryMoveKeyedExpr(IHqlExpression * transformed
         return swapIntoIf(transformed, true);
     case no_nonempty:
     case no_addfiles:
+    case no_chooseds:
         return swapIntoAddFiles(transformed, true);
     //Force the child to be keyed if it is surrounded by something that needs to be keyed, to ensure both migrate up the tree
     case no_hqlproject:
@@ -2606,6 +2607,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             case no_if:
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 return swapIntoAddFiles(transformed);
             case no_sort:
                 {
@@ -2642,6 +2644,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             case no_if:
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 return swapIntoAddFiles(transformed);
             case no_limit:
                 {
@@ -2804,6 +2807,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             case no_if:
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 return swapIntoAddFiles(transformed);
             case no_fetch:
                 if (isPureActivity(child) && !hasUnknownTransform(child))
@@ -2954,6 +2958,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             case no_if:
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 return swapIntoAddFiles(transformed);
             }
             break;
@@ -2973,6 +2978,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
                     break;
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 if (isComplexTransform(transform))
                     break;
                 return swapIntoAddFiles(transformed);
@@ -3165,6 +3171,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
                     break;
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 if (isComplexTransform(transformed->queryChild(2)))
                     break;
                 return swapIntoAddFiles(transformed);
@@ -3468,6 +3475,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             case no_if:
                 return swapIntoIf(transformed);
             case no_nonempty:
+            case no_chooseds:
                 return swapIntoAddFiles(transformed);
             case no_compound_diskread:
             case no_compound_disknormalize:

+ 1 - 0
ecl/hql/hqltrans.cpp

@@ -2182,6 +2182,7 @@ void ConditionalHqlTransformer::doAnalyseExpr(IHqlExpression * expr)
     case no_which:
     case no_rejected:
     case no_choose:
+    case no_chooseds:
         if (treatAsConditional(expr))
         {
             analyseExpr(expr->queryChild(0));

+ 5 - 0
ecl/hql/hqlutil.cpp

@@ -1488,6 +1488,8 @@ unsigned getFirstActivityArgument(IHqlExpression * expr)
     case no_case:
     case no_fetch:
     case no_libraryselect:
+    case no_chooseds:
+    case no_choose:
         return 1;
     }
     return 0;
@@ -1744,6 +1746,7 @@ bool isSinkActivity(IHqlExpression * expr)
     case no_newsoapcall:
     case no_if:
     case no_null:
+    case no_choose:
         return expr->isAction();
     }
     return false;
@@ -1823,6 +1826,8 @@ IHqlExpression * queryChildActivity(IHqlExpression * expr, unsigned index)
     case no_if:
     case no_case:
     case no_fetch:
+    case no_choose:
+    case no_chooseds:
         firstActivityIndex = 1;
         break;
     }

+ 20 - 2
ecl/hqlcpp/hqlcpp.cpp

@@ -619,6 +619,19 @@ IHqlExpression * adjustValue(IHqlExpression * value, __int64 delta)
             }
             break;
         }
+    //optimize no_case because it is generated by a transformation of the dataset no_case
+    case no_case:
+    case no_mapto:
+        {
+            HqlExprArray args;
+            args.append(*LINK(value->queryChild(0)));
+            ForEachChildFrom(i, value, 1)
+            {
+                IHqlExpression * cur = value->queryChild(i);
+                args.append(*adjustValue(cur, delta));
+            }
+            return value->clone(args);
+        }
     }
 
     IHqlExpression * deltaExpr;
@@ -1690,6 +1703,7 @@ void HqlCppTranslator::cacheOptions()
         DebugOption(options.projectNestedTables,"projectNestedTables",true),
         DebugOption(options.showSeqInGraph,"showSeqInGraph",false),  // For tracking down why projects are not commoned up
         DebugOption(options.normalizeSelectorSequence,"normalizeSelectorSequence",false),  // For tracking down why projects are not commoned up
+        DebugOption(options.transformCaseToChoose,"transformCaseToChoose",true),
     };
 
     //get options values from workunit
@@ -6730,8 +6744,12 @@ void HqlCppTranslator::doBuildChoose(BuildCtx & ctx, const CHqlBoundTarget * tar
         buildExprOrAssign(subctx, target, expr->queryChild(idx), NULL);
     }
 
-    subctx.addDefault(stmt);
-    buildExprOrAssign(subctx, target, expr->queryChild(max), NULL);
+    IHqlExpression * defaultExpr = expr->queryChild(max);
+    if (target || !isNullAction(defaultExpr))
+    {
+        subctx.addDefault(stmt);
+        buildExprOrAssign(subctx, target, defaultExpr, NULL);
+    }
 }
 
 void HqlCppTranslator::doBuildAssignChoose(BuildCtx & ctx, const CHqlBoundTarget & target, IHqlExpression * expr)

+ 4 - 0
ecl/hqlcpp/hqlcpp.ipp

@@ -715,6 +715,7 @@ struct HqlCppOptions
     bool                projectNestedTables;
     bool                showSeqInGraph;
     bool                normalizeSelectorSequence;
+    bool                transformCaseToChoose;
 };
 
 //Any information gathered while processing the query should be moved into here, rather than cluttering up the translator class
@@ -1173,6 +1174,7 @@ public:
     void buildSerializedDataset(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt);
 
     void buildDatasetAssignAggregate(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr);
+    void buildDatasetAssignChoose(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr);
     void buildDatasetAssignInlineTable(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr);
     void buildDatasetAssignJoin(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr);
     void buildDatasetAssignProject(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr);
@@ -1345,6 +1347,8 @@ public:
     ABoundActivity * doBuildActivityChildDataset(BuildCtx & ctx, IHqlExpression * expr);
     ABoundActivity * doBuildActivityChildGroupAggregate(BuildCtx & ctx, IHqlExpression * expr);
     ABoundActivity * doBuildActivityChildNormalize(BuildCtx & ctx, IHqlExpression * expr);
+    ABoundActivity * doBuildActivityChoose(BuildCtx & ctx, IHqlExpression * expr, IHqlExpression * cond, CIArrayOf<ABoundActivity> & inputs, bool isRoot);
+    ABoundActivity * doBuildActivityChoose(BuildCtx & ctx, IHqlExpression * expr, bool isRoot);
     ABoundActivity * doBuildActivityChooseSets(BuildCtx & ctx, IHqlExpression * expr);
     ABoundActivity * doBuildActivityChooseSetsEx(BuildCtx & ctx, IHqlExpression * expr);
     ABoundActivity * doBuildActivityCloned(BuildCtx & ctx, IHqlExpression * expr);

+ 50 - 8
ecl/hqlcpp/hqlcppds.cpp

@@ -680,6 +680,29 @@ void HqlCppTranslator::doBuildAssignAggregateLoop(BuildCtx & ctx, const CHqlBoun
             doBuildAssignAggregateLoop(ctx, target, expr, dataset->queryChild(1), doneFirstVar);
             return;
         }
+    case no_chooseds:
+        {
+            CHqlBoundExpr cond;
+            buildExpr(ctx, dataset->queryChild(0), cond);
+
+            IHqlExpression * last = queryLastNonAttribute(dataset);
+            BuildCtx subctx(ctx);
+            IHqlStmt * switchstmt = subctx.addSwitch(cond.expr);
+            ForEachChildFrom(i, dataset, 1)
+            {
+                IHqlExpression * cur = dataset->queryChild(i);
+                if (cur != last)
+                {
+                    OwnedHqlExpr label = getSizetConstant(i);
+                    subctx.addCase(switchstmt, label);
+                }
+                else
+                    subctx.addDefault(switchstmt);
+
+                doBuildAssignAggregateLoop(subctx, target, expr, cur, doneFirstVar);
+            }
+            return;
+        }
     case no_null:
         return;
     }
@@ -2760,6 +2783,29 @@ void HqlCppTranslator::buildDatasetAssignAggregate(BuildCtx & ctx, IHqlCppDatase
     target->finishRow(subctx, targetRow);
 }
 
+void HqlCppTranslator::buildDatasetAssignChoose(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * expr)
+{
+    CHqlBoundExpr cond;
+    buildExpr(ctx, expr->queryChild(0), cond);
+
+    IHqlExpression * last = queryLastNonAttribute(expr);
+    BuildCtx subctx(ctx);
+    IHqlStmt * switchstmt = subctx.addSwitch(cond.expr);
+    ForEachChildFrom(i, expr, 1)
+    {
+        IHqlExpression * cur = expr->queryChild(i);
+        if (cur != last)
+        {
+            OwnedHqlExpr label = getSizetConstant(i);
+            subctx.addCase(switchstmt, label);
+        }
+        else
+            subctx.addDefault(switchstmt);
+
+        buildDatasetAssign(subctx, target, cur);
+    }
+}
+
 
 void HqlCppTranslator::buildDatasetAssign(BuildCtx & ctx, IHqlCppDatasetBuilder * target, IHqlExpression * _expr)
 {
@@ -2805,6 +2851,9 @@ void HqlCppTranslator::buildDatasetAssign(BuildCtx & ctx, IHqlCppDatasetBuilder
             }
         }
         return;
+    case no_chooseds:
+        buildDatasetAssignChoose(subctx, target, expr);
+        return;
     case no_null:
         return;
     case no_activetable:
@@ -3732,18 +3781,11 @@ void HqlCppTranslator::buildRowAssign(BuildCtx & ctx, IReferenceSelector * targe
         return;
     case no_if:
         {
-            OwnedHqlExpr foldedCond = foldHqlExpression(expr->queryChild(0));
-            if (foldedCond->queryValue())
-            {
-                unsigned branch = (foldedCond->queryValue()->getBoolValue()) ? 1 : 2;
-                buildRowAssign(ctx, target, expr->queryChild(branch));
-                return;
-            }
-
             //Assigning a variable size record can mean that references to self need recalculating outside of the condition,
             //producing poor code.
             if (!isVariableSizeRecord(expr->queryRecord()))
             {
+                OwnedHqlExpr foldedCond = foldHqlExpression(expr->queryChild(0));
                 BuildCtx condctx(ctx);
                 IHqlStmt * cond = buildFilterViaExpr(condctx, foldedCond);
 

+ 68 - 5
ecl/hqlcpp/hqlhtcpp.cpp

@@ -6192,6 +6192,10 @@ ABoundActivity * HqlCppTranslator::buildActivity(BuildCtx & ctx, IHqlExpression
             case no_map:
                 result = doBuildActivityCase(ctx, expr, isRoot);
                 break;
+            case no_chooseds:
+            case no_choose:
+                result = doBuildActivityChoose(ctx, expr, isRoot);
+                break;
             case no_iterate:
                 result = doBuildActivityIterate(ctx, expr);
                 break;
@@ -15413,6 +15417,62 @@ ABoundActivity * HqlCppTranslator::doBuildActivitySequentialParallel(BuildCtx &
     return instance->getBoundActivity();
 }
 
+
+ABoundActivity * HqlCppTranslator::doBuildActivityChoose(BuildCtx & ctx, IHqlExpression * expr, IHqlExpression * cond, CIArrayOf<ABoundActivity> & inputs, bool isRoot)
+{
+    Owned<ABoundActivity> boundDefault = &inputs.popGet();
+
+    bool isChild = (insideChildGraph(ctx) || insideRemoteGraph(ctx));
+    assertex(!expr->isAction());
+    ThorActivityKind tak = isChild ? TAKchildcase : TAKcase;
+    Owned<ActivityInstance> instance = new ActivityInstance(*this, ctx, tak, expr, "Case");
+
+    buildActivityFramework(instance, isRoot);
+    buildInstancePrefix(instance);
+
+    BuildCtx funcctx(instance->startctx);
+    funcctx.addQuotedCompound("virtual unsigned getBranch()");
+    OwnedHqlExpr fullCond(foldHqlExpression(cond));
+    if (options.spotCSE)
+        fullCond.setown(spotScalarCSE(fullCond));
+    buildReturn(funcctx, fullCond);
+
+    StringBuffer label;
+    ForEachItemIn(branchIdx, inputs)
+    {
+        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);
+        else
+            buildConnectInputOutput(ctx, instance, boundBranch, 0, branchIdx, label.str());
+    }
+
+    IHqlExpression * activeGraph = queryActiveSubGraph(ctx)->graphTag;
+    bool graphIndependent = isGraphIndependent(fullCond, activeGraph);
+
+    if (graphIndependent && !instance->hasChildActivity)
+        instance->addAttributeBool("_graphIndependent", true);
+
+    buildConnectInputOutput(ctx, instance, boundDefault, 0, inputs.ordinality(), "default");
+
+    buildInstanceSuffix(instance);
+    return instance->getBoundActivity();
+}
+
+ABoundActivity * HqlCppTranslator::doBuildActivityChoose(BuildCtx & ctx, IHqlExpression * expr, bool isRoot)
+{
+    bool isChild = (insideChildGraph(ctx) || insideRemoteGraph(ctx));
+
+    CIArrayOf<ABoundActivity> inputs;
+    ForEachChildFrom(i, expr, 1)
+        inputs.append(*getConditionalActivity(ctx, expr->queryChild(i), isChild));
+
+    OwnedHqlExpr branch = adjustValue(expr->queryChild(0), -1);
+    return doBuildActivityChoose(ctx, expr, branch, inputs, isRoot);
+}
+
+
 ABoundActivity * HqlCppTranslator::doBuildActivityCase(BuildCtx & ctx, IHqlExpression * expr, bool isRoot)
 {
     node_operator op = expr->getOperator();
@@ -15427,7 +15487,6 @@ ABoundActivity * HqlCppTranslator::doBuildActivityCase(BuildCtx & ctx, IHqlExpre
         inputs.append(*getConditionalActivity(ctx, expr->queryChild(iinput)->queryChild(1), isChild));
     Owned<ABoundActivity> boundDefault = getConditionalActivity(ctx, expr->queryChild(max-1), isChild);
         
-    
     ThorActivityKind tak = isChild ? TAKchildcase : TAKcase;
     Owned<ActivityInstance> instance = new ActivityInstance(*this, ctx, tak, expr, "Case");
 
@@ -17920,13 +17979,17 @@ static bool needsRealThor(IHqlExpression *expr, unsigned flags)
         break;
 
     case no_if:
+    case no_choose:
+    case no_chooseds:
         {
             if (needsRealThor(expr->queryChild(0), 0))
                 return true;
-            if (needsRealThor(expr->queryChild(1), flags))
-                return true;
-            IHqlExpression * c2 = expr->queryChild(2);
-            return (c2 && needsRealThor(c2, flags));
+            ForEachChildFrom(i, expr, 1)
+            {
+                if (needsRealThor(expr->queryChild(i), flags))
+                    return true;
+            }
+            return false;
         }
 
     case no_colon:

+ 8 - 2
ecl/hqlcpp/hqlinline.cpp

@@ -241,11 +241,16 @@ static unsigned calcInlineFlags(BuildCtx * ctx, IHqlExpression * expr)
             return ret;
         }
     case no_if:
+    case no_choose:
+    case no_chooseds:
         {
             unsigned ret = expr->isDatarow() ? RETevaluate : RETassign;
-            for (unsigned i=1; i < 3; i++)
+            ForEachChildFrom(i, expr, 1)
             {
-                unsigned childFlags = getInlineFlags(ctx, expr->queryChild(i));
+                IHqlExpression * cur = expr->queryChild(i);
+                if (cur->isAttribute())
+                    continue;
+                unsigned childFlags = getInlineFlags(ctx, cur);
                 if (childFlags == 0)
                     return 0;
                 if (childFlags & HEFspillinline)
@@ -669,6 +674,7 @@ static GraphLocalisation queryGraphLocalisation(IHqlExpression * expr)
     switch (expr->getOperator())
     {
     case no_if:
+    case no_chooseds:
         firstChild = 1;
         numChildren = expr->numChildren();
         break;

+ 1 - 0
ecl/hqlcpp/hqliproj.cpp

@@ -2106,6 +2106,7 @@ ProjectExprKind ImplicitProjectTransformer::getProjectExprKind(IHqlExpression *
     case no_merge:
     case no_nonempty:
     case no_cogroup:
+    case no_chooseds:
         return PassThroughActivity;
     case no_keydiff:
     case no_keypatch:

+ 30 - 12
ecl/hqlcpp/hqlresource.cpp

@@ -2291,9 +2291,10 @@ protected:
         switch (op)
         {
         case no_if:
+        case no_choose:
+        case no_chooseds:
             {
                 IHqlExpression * cond = expr->queryChild(0);
-//              bool condCanBeEvaluated = isEvaluateable(cond, true);
                 analyseExpr(cond);
                 if (expr->isDataset() || expr->isDatarow())
                     conditionalDepth++;
@@ -2814,6 +2815,8 @@ void EclResourcer::createInitialGraph(IHqlExpression * expr, IHqlExpression * ow
                 return;
             }
         case no_if:
+        case no_choose:
+        case no_chooseds:
             //conditional nodes, the child branches are marked as conditional
             childLinkKind = UnconditionalLink;
             thisGraph->mergedConditionSource = true;
@@ -3005,6 +3008,8 @@ void EclResourcer::markAsUnconditional(IHqlExpression * expr, ResourceGraphInfo
     switch (op)
     {
     case no_if:
+    case no_choose:
+    case no_chooseds:
         if (options.noConditionalLinks)
             break;
         if (condition)
@@ -3055,10 +3060,14 @@ void EclResourcer::markAsUnconditional(IHqlExpression * expr, ResourceGraphInfo
 
 void EclResourcer::markConditionBranch(unsigned childIndex, IHqlExpression * expr, IHqlExpression * condition, bool wasConditional)
 {
-    IHqlExpression * child = expr->queryChild(childIndex);
+    IHqlExpression * child = queryRealChild(expr, childIndex);
     if (child)
     {
-        OwnedHqlExpr tag = createAttribute(((childIndex==1) ? trueAtom : falseAtom), LINK(expr), LINK(condition));
+        OwnedHqlExpr tag;
+        if (expr->getOperator() == no_if)
+            tag.setown(createAttribute(((childIndex==1) ? trueAtom : falseAtom), LINK(expr), LINK(condition)));
+        else
+            tag.setown(createAttribute(trueAtom, LINK(expr), LINK(condition), getSizetConstant(childIndex)));
         markAsUnconditional(child, queryResourceInfo(child)->graph, tag);
 
         queryResourceInfo(child)->setConditionSource(tag, !wasConditional);
@@ -3068,8 +3077,8 @@ void EclResourcer::markConditionBranch(unsigned childIndex, IHqlExpression * exp
 
 void EclResourcer::markCondition(IHqlExpression * expr, IHqlExpression * condition, bool wasConditional)
 {
-    markConditionBranch(1, expr, condition, wasConditional);
-    markConditionBranch(2, expr, condition, wasConditional);
+    ForEachChildFrom(i, expr, 1)
+        markConditionBranch(i, expr, condition, wasConditional);
 }
 
 void EclResourcer::markConditions(HqlExprArray & exprs)
@@ -3193,15 +3202,18 @@ bool EclResourcer::calculateResourceSpillPoints(IHqlExpression * expr, ResourceG
         }
     }
 
-    if (expr->getOperator() == no_if)
+    node_operator op = expr->getOperator();
+    if ((op == no_if) || (op == no_choose) || (op == no_chooseds))
     {
         //For conditions, spill on intersection of resources used, not union.
         CResources savedResources(*curResources);
         if (!calculateResourceSpillPoints(expr->queryChild(1), graph, *curResources, hasGoodSpillPoint, true))
-            return false;   
-        if (expr->queryChild(2))
+            return false;
+        ForEachChildFrom(i, expr, 2)
         {
-            if (!calculateResourceSpillPoints(expr->queryChild(2), graph, savedResources, hasGoodSpillPoint, true))
+            if (expr->queryChild(i)->isAttribute())
+                continue;
+            if (!calculateResourceSpillPoints(expr->queryChild(i), graph, savedResources, hasGoodSpillPoint, true))
                 return false;
             curResources->maximize(savedResources);
         }
@@ -3245,15 +3257,19 @@ void EclResourcer::insertResourceSpillPoints(IHqlExpression * expr, IHqlExpressi
     bool ok = info->graph->allocateResources(exprResources, *resourceLimit);
     assertex(ok);
 
-    if (expr->getOperator() == no_if)
+    node_operator op = expr->getOperator();
+    if ((op == no_if) || (op == no_choose) || (op == no_chooseds))
     {
         CResources savedResources(info->graph->resources);
         insertResourceSpillPoints(expr->queryChild(1), expr, originalGraph, info->graph);
-        if (expr->queryChild(2))
+
+        ForEachChildFrom(i, expr, 2)
         {
+            if (expr->queryChild(i)->isAttribute())
+                continue;
             CResources branchResources(info->graph->resources);
             info->graph->resources.set(savedResources);
-            insertResourceSpillPoints(expr->queryChild(2), expr, originalGraph, info->graph);
+            insertResourceSpillPoints(expr->queryChild(i), expr, originalGraph, info->graph);
             info->graph->resources.maximize(branchResources);
         }
     }
@@ -4175,6 +4191,8 @@ IHqlExpression * EclResourcer::doCreateResourced(IHqlExpression * expr, Resource
     switch (op)
     {
     case no_if:
+    case no_choose:
+    case no_chooseds:
         {
             ForEachChild(idx, expr)
             {

+ 0 - 1
ecl/hqlcpp/hqlresource.ipp

@@ -258,7 +258,6 @@ protected:
     IHqlExpression * createSpiller(IHqlExpression * transformed, bool reuseSplitter);
     IHqlExpression * createSplitter(IHqlExpression * transformed);
 
-    
 protected:
     void addSpillFlags(HqlExprArray & args, bool isRead);
     IHqlExpression * createSpillName();

+ 83 - 7
ecl/hqlcpp/hqlttcpp.cpp

@@ -877,6 +877,15 @@ YesNoOption HqlThorBoundaryTransformer::calcNormalizeThor(IHqlExpression * expr)
         }
         // default action.  Not completely convinced it is correct....
         break;
+    case no_choose:
+        if (type && (type->getTypeCode() == type_void))
+        {
+            if (resourceConditionalActions)
+            {
+                UNIMPLEMENTED;
+            }
+        }
+        break;
     case no_setresult:
         {
             IHqlExpression * value = expr->queryChild(0);
@@ -1086,6 +1095,7 @@ IHqlExpression * ThorScalarTransformer::createTransformed(IHqlExpression * expr)
     switch (op)
     {
     case no_if:
+    case no_chooseds:
         {
             bool isGuard = isFailureGuard(expr);
             HqlExprArray children;
@@ -7487,15 +7497,15 @@ void AutoScopeMigrateTransformer::doAnalyseExpr(IHqlExpression * expr)
     case no_sequential:
         return;
     case no_if:
+    case no_choose:
         {
             if (expr->isAction())
             {
                 bool wasConditional = isConditional;
                 analyseExpr(expr->queryChild(0));
                 isConditional = true;
-                analyseExpr(expr->queryChild(1));
-                if (expr->queryChild(2))
-                    analyseExpr(expr->queryChild(2));
+                ForEachChildFrom(i, expr, 1)
+                    analyseExpr(expr->queryChild(i));
                 isConditional = wasConditional;
                 return;
             }
@@ -8795,12 +8805,13 @@ IHqlExpression * HqlScopeTagger::transformNewDataset(IHqlExpression * expr, bool
     switch (op)
     {
     case no_if:
+    case no_chooseds:
         {
             HqlExprArray args;
             args.append(*transform(expr->queryChild(0)));
             args.append(*transformNewDataset(expr->queryChild(1), false));
-            if (expr->queryChild(2))
-                args.append(*transformNewDataset(expr->queryChild(2), false));
+            ForEachChildFrom(i, expr, 2)
+                args.append(*transformNewDataset(expr->queryChild(i), false));
             return expr->clone(args);
         }
     case no_addfiles:
@@ -10096,6 +10107,7 @@ HqlTreeNormalizer::HqlTreeNormalizer(HqlCppTranslator & _translator) : NewHqlTra
     options.constantFoldNormalize = translatorOptions.constantFoldNormalize;
     options.allowActivityForKeyedJoin = translatorOptions.allowActivityForKeyedJoin;
     options.implicitShuffle = translatorOptions.implicitBuildIndexShuffle;
+    options.transformCaseToChoose = translatorOptions.transformCaseToChoose;
     errors = translator.queryErrors();
     nextSequenceValue = 1;
 }
@@ -10421,7 +10433,7 @@ IHqlExpression * HqlTreeNormalizer::transformActionList(IHqlExpression * expr)
 }
 
 
-IHqlExpression * HqlTreeNormalizer::transformCase(IHqlExpression * expr)
+IHqlExpression * HqlTreeNormalizer::transformCaseToIfs(IHqlExpression * expr)
 {
     unsigned max = numRealChildren(expr);
     OwnedHqlExpr testVar = transform(expr->queryChild(0));
@@ -10440,6 +10452,54 @@ IHqlExpression * HqlTreeNormalizer::transformCase(IHqlExpression * expr)
     return elseExpr.getClear();
 }
 
+IHqlExpression * HqlTreeNormalizer::transformCaseToChoose(IHqlExpression * expr)
+{
+    //For the moment only convert datasets to choose format.  (Partly to test implementation.)
+    //Datarows are unlikely to benefit, and will cause additional work.
+    //Converting actions has implications for needing new activity kinds, and support in thor.
+    if (!expr->isDataset() || !options.transformCaseToChoose)
+        return transformCaseToIfs(expr);
+
+    unsigned max = numRealChildren(expr);
+    HqlExprArray branches;
+    OwnedHqlExpr testVar = transform(expr->queryChild(0));
+
+    HqlExprArray caseArgs;
+    caseArgs.append(*LINK(testVar));
+    bool isNullMapping = true;
+    for (unsigned i1=1; i1 < max-1; i1++)
+    {
+        IHqlExpression * mapto = expr->queryChild(i1);
+        OwnedHqlExpr key = transform(mapto->queryChild(0));
+        OwnedHqlExpr branch = transform(mapto->queryChild(1));
+        unsigned matchIndex = branches.find(*branch);
+        if (matchIndex == NotFound)
+        {
+            matchIndex = branches.ordinality();
+            branches.append(*branch.getClear());
+        }
+        OwnedHqlExpr value = getSizetConstant(matchIndex+1);
+        //MORE: Could calculate a delta and add/subtract it from testVar
+        if (!key->queryValue() || !matchesConstantValue(key, matchIndex+1))
+            isNullMapping = false;
+
+        caseArgs.append(*createValue(no_mapto, value->getType(), key.getClear(), LINK(value)));
+    }
+    caseArgs.append(*getSizetConstant(max-1));
+
+    HqlExprArray args;
+    if (isNullMapping)
+        args.append(*LINK(testVar));
+    else
+        args.append(*createValue(no_case, LINK(sizetType), caseArgs));
+    appendArray(args, branches);
+    args.append(*transform(expr->queryChild(max-1)));
+
+    if (expr->isDataset())
+        return createDataset(no_chooseds, args);
+    return createAction(no_choose, args);
+}
+
 IHqlExpression * HqlTreeNormalizer::transformEvaluate(IHqlExpression * expr)
 {
     //Evaluate causes chaos - so translate it to a different form.
@@ -11181,6 +11241,22 @@ IHqlExpression * HqlTreeNormalizer::createTransformed(IHqlExpression * expr)
             }
             break;
         }
+    case no_choose:
+    case no_chooseds:
+        {
+            OwnedHqlExpr cond = transform(expr->queryChild(0));
+            IValue * condValue = cond->queryValue();
+            if (condValue)
+            {
+                unsigned idx = (unsigned)condValue->getIntValue();
+                IHqlExpression * branch = queryRealChild(expr, idx);
+                if (branch)
+                    return transform(branch);
+                IHqlExpression * defaultExpr = queryLastNonAttribute(expr);
+                return transform(defaultExpr);
+            }
+            break;
+        }
     case no_and:
         {
             IHqlExpression * left = expr->queryChild(0);
@@ -11241,7 +11317,7 @@ IHqlExpression * HqlTreeNormalizer::createTransformedBody(IHqlExpression * expr)
         return LINK(expr);          // avoid creating an array in default code...
     case no_case:
         if (isVoidOrDatasetOrList(expr))
-            return transformCase(expr);
+            return transformCaseToChoose(expr);
         break;
     case no_map:
         if (isVoidOrDatasetOrList(expr))

+ 3 - 1
ecl/hqlcpp/hqlttcpp.ipp

@@ -1145,7 +1145,8 @@ protected:
     IHqlExpression * optimizeAssignSkip(HqlExprArray & children, IHqlExpression * expr, IHqlExpression * cond, unsigned depth);
     IHqlExpression * queryTransformPatternDefine(IHqlExpression * expr);
     IHqlExpression * transformActionList(IHqlExpression * expr);
-    IHqlExpression * transformCase(IHqlExpression * expr);
+    IHqlExpression * transformCaseToChoose(IHqlExpression * expr);
+    IHqlExpression * transformCaseToIfs(IHqlExpression * expr);
     IHqlExpression * transformExecuteWhen(IHqlExpression * expr);
     IHqlExpression * transformEvaluate(IHqlExpression * expr);
     IHqlExpression * transformIfAssert(node_operator newOp, IHqlExpression * expr);
@@ -1192,6 +1193,7 @@ protected:
         bool constantFoldNormalize;
         bool allowActivityForKeyedJoin;
         bool implicitShuffle;
+        bool transformCaseToChoose;
     } options;
     unsigned nextSequenceValue;
     bool seenForceLocal;

+ 4 - 5
ecl/hthor/hthor.cpp

@@ -2983,11 +2983,10 @@ void CHThorCaseActivity::ready()
     initialProcessed = processed;
     selectedInput = NULL;
     unsigned whichBranch = helper.getBranch();
-    if (inputs.isItem(whichBranch))
-    {
-        selectedInput = inputs.item(whichBranch);
-        selectedInput->ready();
-    }
+    if (whichBranch >= inputs.ordinality())
+        whichBranch = inputs.ordinality()-1;
+    selectedInput = inputs.item(whichBranch);
+    selectedInput->ready();
 }
 
 void CHThorCaseActivity::done()

+ 32 - 0
ecl/regress/chooseds.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+
+namesRecord :=
+            RECORD
+string20        surname;
+string10        forename;
+integer2        age := 25;
+            END;
+
+namesTable := dataset('x',namesRecord,FLAT);
+
+xval := 1 : stored('xval');
+
+
+output(choose(xval, namesTable(age <18), namesTable(age between 18 and 64), namesTable(age > 65)));

+ 43 - 0
ecl/regress/chooseds2.ecl

@@ -0,0 +1,43 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+zero := 0 : stored('zero');
+one := 1 : stored('one');
+two := 2 : stored('two');
+three := 3 : stored('three');
+five := 5 : stored('five');
+
+
+c0 := CHOOSE(zero, makeDataset(1), makeDataset(2), makeDataset(3));
+c1 := CHOOSE(one, makeDataset(1), makeDataset(2), makeDataset(3));
+c2 := CHOOSE(two, makeDataset(1), makeDataset(2), makeDataset(3));
+c3 := CHOOSE(three, makeDataset(1), makeDataset(2), makeDataset(3));
+c5 := CHOOSE(five, makeDataset(1), makeDataset(2), makeDataset(3));
+
+sequential(
+    output(c0),
+    output(c1),
+    output(c2);
+    output(c3),
+    output(c5);
+    );

+ 32 - 0
ecl/regress/chooseds3.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(row(transform(idRecord, self.id := start)));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2));
+END;
+
+output(PROJECT(nofold(ds1), t(LEFT)));

+ 32 - 0
ecl/regress/chooseds4.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := sort(CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2)), id);
+END;
+
+output(PROJECT(ds1, t(LEFT)));

+ 32 - 0
ecl/regress/chooseds5.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2));
+END;
+
+output(PROJECT(nofold(ds1), t(LEFT)));

+ 32 - 0
ecl/regress/choosedserr.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+
+namesRecord :=
+            RECORD
+string20        surname;
+string10        forename;
+integer2        age := 25;
+            END;
+
+namesTable := dataset('x',namesRecord,FLAT);
+
+xval := 1 : stored('xval');
+
+
+output(choose(xval, group(namesTable(age <18), surname), namesTable(age between 18 and 64), namesTable(age > 65)));

+ 13 - 3
roxie/ccd/ccdserver.cpp

@@ -672,7 +672,7 @@ public:
         return inputs.get(idx, sourceidx);
     }
 
-    inline unsigned numInputs() const { return inputs.ordinality(); }
+    virtual unsigned numInputs() const { return inputs.ordinality(); }
 };
 
 class CWrappedException : public CInterface, implements IException
@@ -727,6 +727,8 @@ public:
         }
         return (unsigned) -1;
     }
+
+    virtual unsigned numInputs() const { return (input == (unsigned)-1) ? 0 : 1; }
 };
 
 class CRoxieServerMultiOutputFactory : public CRoxieServerActivityFactory
@@ -12102,6 +12104,7 @@ public:
         }
     }
 
+    virtual unsigned numInputs() const { return 2; }
 };
 
 IRoxieServerActivityFactory *createRoxieServerJoinActivityFactory(unsigned _id, unsigned _subgraphId, IQueryFactory &_queryFactory, HelperFactory *_helperFactory, ThorActivityKind _kind)
@@ -13147,6 +13150,7 @@ public:
         }
     }
 
+    virtual unsigned numInputs() const { return 2; }
 };
 
 IRoxieServerActivityFactory *createRoxieServerCombineGroupActivityFactory(unsigned _id, unsigned _subgraphId, IQueryFactory &_queryFactory, HelperFactory *_helperFactory, ThorActivityKind _kind)
@@ -15137,7 +15141,7 @@ public:
         return inputs.get(idx, sourceidx);
     }
 
-    inline unsigned numInputs() const { return inputs.ordinality(); }
+    virtual unsigned numInputs() const { return inputs.ordinality(); }
 };
 
 IRoxieServerActivityFactory *createRoxieServerLibraryCallActivityFactory(unsigned _id, unsigned _subgraphId, IQueryFactory &_queryFactory, HelperFactory *_helperFactory, ThorActivityKind _kind, LibraryCallFactoryExtra & _extra)
@@ -18465,7 +18469,9 @@ public:
     {
         CRoxieServerActivity::start(parentExtractSize, parentExtract, paused);
         cond = helper.getBranch();
-        assertex(cond < numInputs);
+        //CHOOSE defaults to the last argument if out of range.
+        if (cond >= numInputs)
+            cond = numInputs - 1;
         inputs[cond]->start(parentExtractSize, parentExtract, paused);
         for (unsigned idx = 0; idx < numInputs; idx++)
         {
@@ -18704,6 +18710,8 @@ public:
         }
     }
 
+    virtual unsigned numInputs() const { return (input2 == (unsigned)-1) ? 1 : 2; }
+
     virtual bool isGraphInvariant() const
     {
         return graphInvariant;
@@ -27107,6 +27115,8 @@ public:
                         break;
                     case TAKcase: case TAKchildcase:
                         branch = static_cast<IHThorCaseArg *>(helper.get())->getBranch();
+                        if (branch >= donor.numInputs())
+                            branch = donor.numInputs() - 1;
                         break;
                     default:
                         throwUnexpected();

+ 1 - 0
roxie/ccd/ccdserver.hpp

@@ -298,6 +298,7 @@ interface IRoxieServerActivityFactory : extends IActivityFactory
     virtual IRoxieServerSideCache *queryServerSideCache() const = 0;
     virtual IDefRecordMeta *queryActivityMeta() const = 0;
     virtual void noteStatistic(unsigned statCode, unsigned __int64 value, unsigned count) const = 0;
+    virtual unsigned numInputs() const = 0;
 };
 interface IGraphResult : public IInterface
 {

+ 43 - 0
testing/ecl/chooseds2.ecl

@@ -0,0 +1,43 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+zero := 0 : stored('zero');
+one := 1 : stored('one');
+two := 2 : stored('two');
+three := 3 : stored('three');
+five := 5 : stored('five');
+
+
+c0 := CHOOSE(zero, makeDataset(1), makeDataset(2), makeDataset(3));
+c1 := CHOOSE(one, makeDataset(1), makeDataset(2), makeDataset(3));
+c2 := CHOOSE(two, makeDataset(1), makeDataset(2), makeDataset(3));
+c3 := CHOOSE(three, makeDataset(1), makeDataset(2), makeDataset(3));
+c5 := CHOOSE(five, makeDataset(1), makeDataset(2), makeDataset(3));
+
+sequential(
+    output(c0),
+    output(c1),
+    output(c2);
+    output(c3),
+    output(c5);
+    );

+ 32 - 0
testing/ecl/chooseds3.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(row(transform(idRecord, self.id := start)));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2));
+END;
+
+output(PROJECT(nofold(ds1), t(LEFT)));

+ 32 - 0
testing/ecl/chooseds4.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := sort(CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2)), id);
+END;
+
+output(PROJECT(ds1, t(LEFT)));

+ 32 - 0
testing/ecl/chooseds5.ecl

@@ -0,0 +1,32 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+idRecord := { unsigned id; };
+xRecord := { unsigned id; dataset(idRecord) ids; };
+
+makeDataset(unsigned start) := dataset(3, transform(idRecord, self.id := start + counter));
+
+
+ds1 := dataset([0,1,2,3,5], idRecord);
+
+xRecord t(idRecord l) := TRANSFORM
+    SELF.id := l.id;
+    SELF.ids := CHOOSE(l.id, makeDataset(l.id), makeDataset(l.id+1), makeDataset(l.id+2));
+END;
+
+output(PROJECT(nofold(ds1), t(LEFT)));

+ 25 - 0
testing/ecl/key/chooseds2.xml

@@ -0,0 +1,25 @@
+<Dataset name='Result 1'>
+ <Row><id>4</id></Row>
+ <Row><id>5</id></Row>
+ <Row><id>6</id></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><id>2</id></Row>
+ <Row><id>3</id></Row>
+ <Row><id>4</id></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><id>3</id></Row>
+ <Row><id>4</id></Row>
+ <Row><id>5</id></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><id>4</id></Row>
+ <Row><id>5</id></Row>
+ <Row><id>6</id></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><id>4</id></Row>
+ <Row><id>5</id></Row>
+ <Row><id>6</id></Row>
+</Dataset>

+ 7 - 0
testing/ecl/key/chooseds3.xml

@@ -0,0 +1,7 @@
+<Dataset name='Result 1'>
+ <Row><id>0</id><ids><Row><id>2</id></Row></ids></Row>
+ <Row><id>1</id><ids><Row><id>1</id></Row></ids></Row>
+ <Row><id>2</id><ids><Row><id>3</id></Row></ids></Row>
+ <Row><id>3</id><ids><Row><id>5</id></Row></ids></Row>
+ <Row><id>5</id><ids><Row><id>7</id></Row></ids></Row>
+</Dataset>

+ 7 - 0
testing/ecl/key/chooseds4.xml

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

+ 7 - 0
testing/ecl/key/chooseds5.xml

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

+ 2 - 0
thorlcr/graph/thgraph.cpp

@@ -646,6 +646,8 @@ bool CGraphElementBase::prepareContext(size32_t parentExtractSz, const byte *par
                 onStart(parentExtractSz, parentExtract);
                 IHThorCaseArg *helper = (IHThorCaseArg *)baseHelper.get();
                 whichBranch = helper->getBranch();
+                if (whichBranch >= inputs.ordinality())
+                    whichBranch = inputs.ordinality()-1;
                 if (inputs.queryItem(whichBranch))
                     return inputs.item(whichBranch)->activity->prepareContext(parentExtractSz, parentExtract, checkDependencies, false, async);
                 return true;