瀏覽代碼

Merge pull request #8807 from ghalliday/issue15767

HPCC-15767 Refactor some of the volatile code in preparation for HPCC-2946

Reviewed-By: Shamser Ahmed <shamser.ahmed@lexisnexis.co.uk>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 8 年之前
父節點
當前提交
17c7a5fa30

+ 10 - 0
ecl/hql/hqlatoms.cpp

@@ -118,6 +118,7 @@ IAtom * _container_Atom;
 IAtom * contextAtom;
 IAtom * contextSensitiveAtom;
 IAtom * costAtom;
+IAtom * costlyAtom;
 IAtom * countAtom;
 IAtom * counterAtom;
 IAtom * _countProject_Atom;
@@ -283,9 +284,11 @@ IAtom * _nlpParse_Atom;
 IAtom * noBoundCheckAtom;
 IAtom * noCaseAtom;
 IAtom * noConstAtom;
+IAtom * _noDuplicate_Atom;
 IAtom * nofoldAtom;
 IAtom * _noHoist_Atom;
 IAtom * noLocalAtom;
+IAtom * noMoveAtom;
 IAtom * _nonEmpty_Atom;
 IAtom * noOverwriteAtom;
 IAtom * _normalized_Atom;
@@ -335,6 +338,7 @@ IAtom * _propAligned_Atom;
 IAtom * _propRecordCount_Atom;
 IAtom * _propSize_Atom;
 IAtom * _propUnadorned_Atom;
+IAtom* _pseudoAction_Atom;
 IAtom * pseudoentrypointAtom;
 IAtom * pullAtom;
 IAtom * pulledAtom;
@@ -441,6 +445,7 @@ IAtom * versionAtom;
 IAtom * virtualAtom;
 IAtom * _virtualSeq_Atom;
 IAtom * volatileAtom;
+IAtom * _volatileId_Atom;
 IAtom * warningAtom;
 IAtom * webserviceAtom;
 IAtom * wholeAtom;
@@ -564,6 +569,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEATOM(context);
     MAKEATOM(contextSensitive);
     MAKEATOM(cost);
+    MAKEATOM(costly);
     MAKEATOM(count);
     MAKEATOM(counter);
     MAKESYSATOM(countProject);
@@ -730,9 +736,11 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEATOM(noBoundCheck);
     MAKEATOM(noCase);
     MAKEATOM(noConst);
+    MAKESYSATOM(noDuplicate);
     MAKEATOM(nofold);
     MAKESYSATOM(noHoist);
     MAKEATOM(noLocal);
+    MAKEATOM(noMove);
     MAKESYSATOM(nonEmpty);
     MAKEATOM(noOverwrite);
     MAKESYSATOM(normalized);
@@ -784,6 +792,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKESYSATOM(propUnadorned);
     MAKEATOM(prototype);
     MAKEATOM(proxyAddress);
+    MAKESYSATOM(pseudoAction);
     MAKEATOM(pseudoentrypoint);
     MAKEATOM(pull);
     MAKEATOM(pulled);
@@ -887,6 +896,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEATOM(virtual);
     MAKESYSATOM(virtualSeq);
     MAKEATOM(volatile);
+    MAKESYSATOM(volatileId);
     MAKEATOM(warning);
     MAKEATOM(webservice);
     MAKEATOM(whole);

+ 5 - 0
ecl/hql/hqlatoms.hpp

@@ -122,6 +122,7 @@ extern HQL_API IAtom * _container_Atom;
 extern HQL_API IAtom * contextAtom;
 extern HQL_API IAtom * contextSensitiveAtom;
 extern HQL_API IAtom * costAtom;
+extern HQL_API IAtom * costlyAtom;
 extern HQL_API IAtom * countAtom;
 extern HQL_API IAtom * counterAtom;
 extern HQL_API IAtom * _countProject_Atom;
@@ -287,9 +288,11 @@ extern HQL_API IAtom * _nlpParse_Atom;
 extern HQL_API IAtom * noBoundCheckAtom;
 extern HQL_API IAtom * noCaseAtom;
 extern HQL_API IAtom * noConstAtom;
+extern HQL_API IAtom * _noDuplicate_Atom;
 extern HQL_API IAtom * nofoldAtom;
 extern HQL_API IAtom * _noHoist_Atom;
 extern HQL_API IAtom * noLocalAtom;
+extern HQL_API IAtom * noMoveAtom;
 extern HQL_API IAtom * _nonEmpty_Atom;
 extern HQL_API IAtom * noOverwriteAtom;
 extern HQL_API IAtom * _normalized_Atom;
@@ -340,6 +343,7 @@ extern HQL_API IAtom * _propAligned_Atom;
 extern HQL_API IAtom * _propRecordCount_Atom;
 extern HQL_API IAtom * _propSize_Atom;
 extern HQL_API IAtom * _propUnadorned_Atom;
+extern HQL_API IAtom * _pseudoAction_Atom;
 extern HQL_API IAtom * pseudoentrypointAtom;
 extern HQL_API IAtom * pullAtom;
 extern HQL_API IAtom * pulledAtom;
@@ -446,6 +450,7 @@ extern HQL_API IAtom * versionAtom;
 extern HQL_API IAtom * virtualAtom;
 extern HQL_API IAtom * _virtualSeq_Atom;
 extern HQL_API IAtom * volatileAtom;
+extern HQL_API IAtom * _volatileId_Atom;
 extern HQL_API IAtom * warningAtom;
 extern HQL_API IAtom * webserviceAtom;
 extern HQL_API IAtom * wholeAtom;

+ 422 - 40
ecl/hql/hqlexpr.cpp

@@ -125,6 +125,204 @@ static int checkSeqId(unsigned __int64 seqid, unsigned why)
 
 #define STDIO_BUFFSIZE 0x10000     // 64K
 
+//---------------------------------------------------------------------------------------------------------------------
+
+/*
+
+There is a general issue with ECL (and other functional/declarative languages) about what to do with impure functions.
+Generally it is assumed that expressions can be evaluated on demand, evaluated more than once, not evaluated,
+evaluated in a different place, and that it will not affect the result of the query.  There are some expressions
+that don't follow those rules and cause problems.
+The following aims to describe the issues, and formalize the behaviour@
+
+Different impure modifiers
+- VOLATILE indicates that an expression may return a different value each time it is called.
+  E.g.,  RANDOM(), msTick()
+  Because volatile expressions return a different value each time, by default they are tagged as context
+  sensitive - to try and ensure they are evaluated in the same place as they were used in the source code.  So
+  volatile is expanded as two separate modifiers - NODUPLICATE and CONTEXT
+- CONTEXT indicates the value returned depends on the context (but is non-volatile within that context)
+  E.g., std.system.thorlib.node(), XMLTEXT
+- THROWS indicates an expression might throw an exception.
+  IF (cond, value, FAIL)
+- SKIPS indicates an expression may cause a transform to skip.
+- COSTLY The operation is expensive, so should not be duplicated.
+  E.g., Some PIPE/SOAPCALLs, external function calls.
+  A first step towards introducing a cost() function - where costly = cost(+inf)
+- EFFECT indicates the expression may have a side-effect.  The side-effect is tied to the expression that it is
+  associated with.  It would have implications for ordering, which we currently make no guarantees about.  Also,
+  EVALUATE(EFFECT) should be forced to evaluate, rather than being optimized away.
+
+Pseudo modifier:
+- once [ Implies pure,fold(false) ]
+- action indicates the expression performs a specific (costly?) action.  Equivalent to COSTLY+EFFECT
+- volatile.  Really a combination of NODUPLICATE and CONTEXT
+
+What decisions do the flags affect?
+
+canRemoveEvaluation()   - Is it ok to not evaluate an expression?
+canReduceEvaluations()  - Is it possible to reduce the number of times something is evaluated?
+canDuplicateExpr()      - Whether an expression can be duplicated.
+canChangeContext()      - Whether an expression can be moved to a different context.
+canRemoveGuard()        - Is it ok to evaluate this expression without any surrounding conditions?
+isVolatile()            - Whether an expression always generates the same value. (E.g., for matching distributions)
+canBeCommonedUp()       - Is it ok to evaluate two instances of the same expression only once?
+canBeReordered()        - Is it possible to reorder evaluation?
+
+How do these decisions relate to the modifiers?
+
+canRemoveEvaluation()
+- the whole system is based around lazy evaluation.  Nothing restricts an expressions from not being evaluated.
+
+canReduceNumberEvaluations()
+- noduplicate... yes
+  Say you have a counter which is assigned to rows in a dataset, and one row is then selected.  If only that single row
+  is calculated you will get a different result.  However lazy evaluation should ensure that is ok, just unexpected.
+  The context may also require checking for duplication if the dataset is shared...
+- otherwise - yes.
+  i.e. *all* expressions are lazy - there are no guarantees that an expression will be evaluated.
+
+canDuplicateExpr()
+- noduplicate - no since that will introduce an inconsistency.  This means volatile rows can only be selected
+  from a dataset if it is the only use of the dataset.
+- context - yes if same context.
+- throws - yes
+- skips - yes
+- costly - no
+- effect - yes
+
+canChangeContext/canHoist
+- noduplicate - yes.  (volatile would also set context, implying no since that may change the number of times something is executed).
+- context - no
+- throws - safer to say no.  What if it causes something to fail because of early evaluation?
+           Better would be to allow it, but only report the error if it is actually used.  This has implications for
+           the way results are stored in the workunit, and the implementation of the engines.
+- skips - no (but skips doesn't percolate outside a transform)
+- costly - yes if unconditional. no if conditional - we don't want it evaluated unnecessarily.
+- effect - yes - it is the expression that is important.
+
+canRemoveGuard (make something unconditional that was conditional)
+- noduplicate - possibly/yes.  It would be better to always evaluate than to evaluate multiple times.  The context is handled separately.
+- context - yes.
+- throws - no since it causes failures that wouldn't otherwise occur
+- skips - no, it could records to be lost.
+- costly - no by definition.
+- effect - yes.
+
+isVolatile()
+- Only set if the expression is volatile.  Equivalent to !canDuplicateExpr()
+
+canBeCommonedUpBetweenContexts()
+- noduplicate - This is explicitly managed by ensuring each volatile expression has a unique attribute associated with it.
+  It means that different instances of a volatile expression in different transforms must have different ids
+  so that combining transforms doesn't cause them to be combined.
+- context - ?no.  The same value evaluated in a different context will give a different value.
+- throws - yes
+- skips - yes
+- costly - yes.
+- effect - yes
+
+canCombineTransforms(a,b)
+- all - yes
+- provided volatile expressions are unique there shouldn't be any problems combining them.
+- still need to be careful about SKIPs having a different meaning in the combined transform.
+
+canBeReordered()
+ - we currently make no guarantees about the order that expressions are evaluated in, other than with
+   the SEQUENTIAL keyword, and implicit ordering of rows supplied to APPLY/OUTPUT.  Restricting the order would
+   cause significant issues with optimization (e.g., executing on multiple nodes, or strands within a channel). It
+   would require something similar to Haskell monads to impose some global ordering.
+
+Reducing the context dependency of expressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The operator no_within(expression, context) has the effect of removing all the context-dependent attributes, and adds
+any dependencies from the context instead.  (Any explicit dependencies of the expression are also kept.)
+Note:  WITHIN can only be used to *reduce* the context-dependency.
+
+RANDOM() WITHIN {LEFT} - indicate the context for calling random.
+
+What should be the scope/extent of their effects?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Theoretically, each of these "impure" attributes are inherited by any expression that uses them.  However that can
+be too conservative, so the following limits are placed on their scope:
+
+noduplicate - always
+context     - always, except for within().  ?possibly not outside a transform, or dataset/action excluding its inputs.
+costly      - always
+action
+throwscalar - not outside transform/filter
+throwds     - not outside transform.  Not outside the action (e.g., output) that consumes it.
+skip        - no outside transform
+
+
+**************** THIS NEEDS MORE THOUGHT WORK - probably inspired from the examples *************************
+
+- A sink (e.g., OUTPUT), row selector ([]), or scalar aggregate (e.g., count(ds)) that is applied to a noduplicate dataset isn't itself noduplicate.
+- A sink (e.g., output) applied to a volatile expression isn't itself volatile.
+- An aggregate is not volatile if the scalar argument is volatile
+- Attributes are not volatile if their arguments are
+- ??? An activity that contains a volatile scalar item isn't itself volatile?  E.g., ds(id != RANDOM()).  I'm not convinced.
+
+For example this means IF(cond, ds, FAIL) will be context dependent.  But the activity (e.g, OUTPUT) that is based on it is not.  The entire OUTPUT could be evaluated elsewhere (e.g., in a parent context) if there are no other dependencies on the context.
+
+I would be inclined to use the same rule for context sensitive expressions and exceptions.
+
+Essentially the rule is:
+- the impure flags are not inherited from a transform
+- actions and attributes inherit no impure flags.  (They could possibly have them set explicitly.)
+
+What makes a unique volatile instance?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Each instance in the original ECL source code creates a unique instance.
+- Each expansion of a macro counts as new source instance.
+- A call to a function containing a volatile should not create new instances.
+- It is possible to mark functions as volatile, so that each call creates a new unique instance of
+  any volatiles within it.
+
+So RANDOM() - RANDOM() should evaluate two random numbers,
+and x:= RANDOM(); x - x; should always evaluate to 0.
+
+So unique volatile identifiers are added to
+- volatile builtin operators (e.g, RANDOM())
+- volatile c++ functions
+- volatile external functions
+and contained volatile modifiers are made unique if a functional definition is specified as volatile.
+
+Modifiers on external functions and beginc++
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+- pure
+- action
+- costly
+- once = pure, runtime only
+- volatile = nodup, context
+- nomove = context dependent
+- context(xxx)?
+- fail
+
+Context Dependent:
+~~~~~~~~~~~~~~~~~~
+There are several different flags to indicate context dependent:
+
+HEFgraphDependent - loop counter (?) graph result, (parameter!) - should probably use a pseudo table
+HEFcontainsNlpText - should use a pseudo table
+HEFcontainsXmlText - should use a pseudo table
+HEFcontainsSkip
+HEFcontainsCounter  - should use a pseudo table
+HEFtransformDependent - SELF, count(group)
+HEFtranslated
+HEFonFailDependent - FAILCODE/FAILMESSAGE
+HEFcontextDependentException - fields, pure virtual  [nohoist?]
+HEFoldthrows - legacy and should be killed
+
+Other related syntax
+~~~~~~~~~~~~~~~~~~~~
+PURE(expression) - treat an expression as pure - probably superseded with WITHIN {}
+*/
+
+//---------------------------------------------------------------------------------------------------------------------
+
 class HqlExprCache : public JavaHashTableOf<IHqlExpression>
 {
 public:
@@ -3226,7 +3424,7 @@ void CHqlExpression::initFlagsBeforeOperands()
         break;
     case no_random:
         infoFlags2 &= ~(HEF2constant);
-        infoFlags |= HEFvolatile;
+        infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
         break;
     case no_wait:
         infoFlags2 |= HEF2globalAction;
@@ -3405,7 +3603,7 @@ void CHqlExpression::updateFlagsAfterOperands()
     switch (op)
     {
     case no_pure:
-        infoFlags &= ~(HEFvolatile|HEFaction|HEFthrowds|HEFthrowscalar|HEFcontainsSkip);
+        infoFlags &= ~(HEFnoduplicate|HEFaction|HEFthrowds|HEFthrowscalar|HEFcontainsSkip);
         break;
     case no_record:
         {
@@ -3552,9 +3750,14 @@ void CHqlExpression::updateFlagsAfterOperands()
         infoFlags2 |= HEF2constant;
         break;
     case no_attr:
-        infoFlags = (infoFlags & (HEFhousekeeping|HEFalwaysInherit));
-        infoFlags2 |= HEF2constant;
-        break;
+        {
+            infoFlags = (infoFlags & (HEFhousekeeping|HEFalwaysInherit));
+            infoFlags2 |= HEF2constant;
+            IAtom * name = queryName();
+            if (name == _volatileId_Atom)
+                infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
+            break;
+        }
     case no_newxmlparse:
     case no_xmlparse:
         //clear flag unless set in the dataset.
@@ -3584,15 +3787,18 @@ void CHqlExpression::updateFlagsAfterOperands()
         break;
     case no_attr_link:
     case no_attr_expr:
-        if (queryName() == onFailAtom)
         {
-            infoFlags &= ~(HEFonFailDependent|HEFcontainsSkip); // ONFAIL(SKIP) - skip shouldn't extend any further
+            IAtom * name = queryName();
+            if (name == onFailAtom)
+                infoFlags &= ~(HEFonFailDependent|HEFcontainsSkip); // ONFAIL(SKIP) - skip shouldn't extend any further
+            else if (name == _volatileId_Atom)
+                infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
+            infoFlags &= ~(HEFthrowscalar|HEFthrowds|HEFoldthrows);
+            break;
         }
-        infoFlags &= ~(HEFthrowscalar|HEFthrowds|HEFoldthrows);
-        break;
     case no_clustersize:
         //wrong, but improves the generated code
-        infoFlags |= HEFvolatile;
+        infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
         break;
     case no_type:
         {
@@ -3652,7 +3858,7 @@ void CHqlExpression::updateFlagsAfterOperands()
                 if (bodycode->getOperator() == no_embedbody)
                 {
                     if (bodycode->queryAttribute(actionAtom))
-                        infoFlags |= HEFvolatile;
+                        infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
                 }
             }
             else
@@ -3663,13 +3869,13 @@ void CHqlExpression::updateFlagsAfterOperands()
     case no_getresult:
         {
             if (false && matchesConstantValue(queryAttributeChild(this, sequenceAtom, 0), ResultSequenceOnce))
-                infoFlags |= HEFvolatile;
+                infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
             break;
         }
     case no_embedbody:
         {
             if (queryAttribute(actionAtom))
-                infoFlags |= HEFvolatile;
+                infoFlags |= (HEFnoduplicate|HEFcontextDependentException);
             break;
         }
     }
@@ -3929,6 +4135,17 @@ void CHqlExpression::onAppendOperand(IHqlExpression & child, unsigned whichOpera
     unsigned childFlags = child.getInfoFlags();
     unsigned childFlags2 = child.getInfoFlags2();
     node_operator childOp = child.getOperator();
+
+    const unsigned contextFlags = HEFcontextDependentException|HEFthrowscalar|HEFthrowds;
+    if (childFlags & contextFlags)
+    {
+        if (isDataset() || isAction())
+        {
+            if (!child.isDataset() && !child.isAction())
+                childFlags &= ~contextFlags;
+        }
+    }
+
     switch (op)
     {
     case no_keyindex:
@@ -3993,7 +4210,7 @@ void CHqlExpression::onAppendOperand(IHqlExpression & child, unsigned whichOpera
     {
     case no_transform:
     case no_newtransform:
-        childFlags &= ~(HEFtransformDependent|HEFcontainsSkip|HEFthrowscalar|HEFthrowds);
+        childFlags &= ~(HEFcontextDependentException|HEFtransformDependent|HEFcontainsSkip|HEFthrowscalar|HEFthrowds);
         break;
     }
 
@@ -4144,7 +4361,7 @@ unsigned CHqlExpression::getCachedEclCRC()
     case no_attr_link:
         {
             const IAtom * name = queryBody()->queryName();
-            if (name == _uid_Atom)
+            if (name == _uid_Atom || name == _volatileId_Atom)
                 return 0;
             const char * nameText = str(name);
             crc = hashnc((const byte *)nameText, strlen(nameText), crc);
@@ -7908,7 +8125,10 @@ IHqlExpression * createFunctionDefinition(IIdAtom * id, IHqlExpression * value,
     if (defaults)
         args.append(*defaults);
     if (attrs)
+    {
         attrs->unwindList(args, no_comma);
+        ::Release(attrs);
+    }
     return createFunctionDefinition(id, args);
 }
 
@@ -9736,7 +9956,7 @@ IHqlExpression * CHqlScopeParameter::lookupSymbol(IIdAtom * searchName, unsigned
 //==============================================================================================================
 
 CHqlDelayedScope::CHqlDelayedScope(HqlExprArray &_ownedOperands)
- : CHqlExpressionWithTables(no_delayedscope)
+ : CHqlExpressionWithTables(no_delayedscope), type(nullptr)
 {
     setOperands(_ownedOperands); // after type is initialized
     type = queryChild(0)->queryType();
@@ -10061,7 +10281,7 @@ IHqlExpression *CHqlSequence::clone(HqlExprArray &newkids)
 
 StringBuffer &CHqlSequence::toString(StringBuffer &ret)
 {
-    return ret.append(name);
+    return ret.append(name).append(":").append(seq);
 }
 
 //==============================================================================================================
@@ -10083,28 +10303,89 @@ CHqlExternal *CHqlExternal::makeExternalReference(IIdAtom * _id, ITypeInfo *_typ
 
 //==============================================================================================================
 
+extern bool isVolatileFuncdef(IHqlExpression * funcdef)
+{
+    if (funcdef->hasAttribute(volatileAtom))
+        return true;
+
+    IHqlExpression * body = funcdef->queryChild(0);
+    switch (body->getOperator())
+    {
+    case no_external:
+        {
+            if (body->hasAttribute(volatileAtom))
+                return true;
+            return false;
+        }
+    case no_outofline:
+        {
+            //Out of line volatile c++ functions create new instances each time they are called.
+            //otherwise it requires an explicit volatile qualifier.
+            IHqlExpression * bodycode = body->queryChild(0);
+            if (bodycode->getOperator() == no_embedbody)
+                return bodycode->queryAttribute(volatileAtom);
+            return false;
+        }
+    default:
+        return false;
+    }
+}
+
 CHqlExternalCall::CHqlExternalCall(IHqlExpression * _funcdef, ITypeInfo * _type, HqlExprArray &_ownedOperands) : CHqlExpressionWithType(no_externalcall, _type, _ownedOperands), funcdef(_funcdef)
 {
-    IHqlExpression * def = funcdef->queryChild(0);
+    IHqlExpression * body = funcdef->queryChild(0);
+    unsigned impureFlags = 0;
+    if (body->hasAttribute(failAtom))
+        impureFlags |= isDataset() ? HEFthrowds : HEFthrowscalar;
+    if (body->hasAttribute(noMoveAtom) || body->hasAttribute(contextSensitiveAtom))
+        impureFlags |= HEFcontextDependentException;
+    if (body->hasAttribute(costlyAtom))
+        impureFlags |= HEFcostly;
+    if (body->hasAttribute(_noDuplicate_Atom))
+        impureFlags |= HEFnoduplicate;
+
+    if (isVolatileFuncdef(funcdef))
+        impureFlags |= (HEFnoduplicate|HEFcontextDependentException);
     //Once aren't really pure, but are as far as the code generator is concerned.  Split into more flags if it becomes an issue.
-    if (!def->hasAttribute(pureAtom) && !def->hasAttribute(onceAtom))
+    if (!body->hasAttribute(pureAtom) && !body->hasAttribute(onceAtom))
     {
-        infoFlags |= (HEFvolatile);
+        infoFlags |= (HEFnoduplicate);
     }
+
+    //Special case built in context functions for backward compatibility
+    if (body->hasAttribute(ctxmethodAtom))
+    {
+        StringBuffer entrypoint;
+        getStringValue(entrypoint, queryAttributeChild(body, entrypointAtom, 0));
+        if (streq(entrypoint.str(), "getNodeNum") ||
+            streq(entrypoint.str(), "getFilePart"))
+        {
+            impureFlags |= HEFcontextDependentException;
+        }
+        if (streq(entrypoint.str(), "getPlatform"))
+        {
+            //impureFlags |= (HEFvolatilevalue|HEFcontextDependentException);
+        }
+    }
+
+    infoFlags |= impureFlags;
     
-    if (def->hasAttribute(actionAtom) || (type && type->getTypeCode() == type_void))
+    if (body->hasAttribute(actionAtom) || (type && type->getTypeCode() == type_void))
         infoFlags |= HEFaction;
 
-    if (def->hasAttribute(userMatchFunctionAtom))
+    if (body->hasAttribute(userMatchFunctionAtom))
     {
         infoFlags |= HEFcontainsNlpText;
     }
 
-    if (def->hasAttribute(contextSensitiveAtom))
-        infoFlags |= HEFcontextDependentException;
-
-    if (def->hasAttribute(ctxmethodAtom) || def->hasAttribute(gctxmethodAtom) || def->hasAttribute(globalContextAtom) || def->hasAttribute(contextAtom))
+    if (body->hasAttribute(ctxmethodAtom) || body->hasAttribute(gctxmethodAtom) || body->hasAttribute(globalContextAtom) || body->hasAttribute(contextAtom))
         infoFlags |= HEFaccessRuntimeContext;
+
+    if (hasAttribute(_pseudoAction_Atom))
+    {
+        ::Release(type);
+        type = makeVoidType();
+    }
 }
 
 bool CHqlExternalCall::equals(const IHqlExpression & other) const
@@ -11378,8 +11659,6 @@ IHqlExpression * ParameterBindTransformer::createExpandedCall(IHqlExpression * c
 {
     HqlExprArray actuals;
     unwindChildren(actuals, call);
-    while (actuals.ordinality() && actuals.tos().isAttribute())
-        actuals.pop();
 
     IHqlExpression * funcdef = call->queryBody()->queryFunctionDefinition();
     return createExpandedCall(funcdef, actuals);
@@ -11389,8 +11668,14 @@ IHqlExpression * ParameterBindTransformer::createExpandedCall(IHqlExpression *fu
 {
     assertex(funcdef->getOperator() == no_funcdef);
     IHqlExpression * formals = funcdef->queryChild(1);
-    assertex(formals->numChildren() == resolvedActuals.length());
-    ForEachItemIn(i, resolvedActuals)
+
+    unsigned numFormals = formals->numChildren();
+    assertex(numFormals <= resolvedActuals.length());
+
+    for (unsigned i1 = numFormals; i1 < resolvedActuals.length(); i1++)
+        assertex(resolvedActuals.item(i1).isAttribute());
+
+    ForEachChild(i, formals)
     {
         IHqlExpression * formal = formals->queryChild(i);
         IHqlExpression * actual = &resolvedActuals.item(i);
@@ -11423,6 +11708,46 @@ extern HQL_API bool isKey(IHqlExpression * expr)
 
 //-------------------------------------------------------------------------------------
 
+static HqlTransformerInfo volatileIdModifierInfo("VolatileIdModifier");
+class VolatileIdModifier : public QuickHqlTransformer
+{
+public:
+    VolatileIdModifier(IHqlExpression * _volatileid)
+    : QuickHqlTransformer(volatileIdModifierInfo, NULL), volatileid(_volatileid)
+    {
+    }
+
+protected:
+    virtual IHqlExpression * createTransformedBody(IHqlExpression * expr)
+    {
+        if (expr->getOperator() == no_outofline)
+            return LINK(expr);
+
+        if (expr->isAttribute() && (expr->queryName() == _volatileId_Atom))
+        {
+            HqlExprArray args;
+            args.append(*LINK(expr));
+            args.append(*LINK(volatileid));
+            return createExprAttribute(_volatileId_Atom, args);
+        }
+
+        return QuickHqlTransformer::createTransformedBody(expr);
+    }
+
+protected:
+    IHqlExpression * volatileid;
+};
+
+IHqlExpression * modifyVolatileIds(IHqlExpression * expr, IHqlExpression * volatileid)
+{
+    VolatileIdModifier modifier(volatileid);
+    return modifier.transform(expr);
+}
+
+
+
+//-------------------------------------------------------------------------------------
+
 static HqlTransformerInfo callExpandTransformerInfo("CallExpandTransformer");
 class CallExpandTransformer : public QuickHqlTransformer
 {
@@ -11666,8 +11991,15 @@ static IHqlExpression * createNormalizedCall(IHqlExpression *funcdef, const HqlE
 
 inline IHqlExpression * expandFunctionalCallBody(CallExpansionContext & ctx, IHqlExpression * call)
 {
-    ParameterBindTransformer binder(ctx, call);
-    return binder.createExpandedCall(call);
+    OwnedHqlExpr ret;
+    {
+        ParameterBindTransformer binder(ctx, call);
+        ret.setown(binder.createExpandedCall(call));
+    }
+    IHqlExpression * volatileid = call->queryAttribute(_volatileId_Atom);
+    if (volatileid)
+        return modifyVolatileIds(ret, volatileid);
+    return ret.getClear();
 }
 
 static IHqlExpression * normalizeTrailingAttributes(IHqlExpression * call)
@@ -12227,11 +12559,12 @@ IHqlExpression * createExternalFuncdefFromInternal(IHqlExpression * funcdef)
     HqlExprArray attrs;
     unwindChildren(attrs, body, 1);
 
-    if (body->isPure())
-        attrs.append(*createAttribute(pureAtom));
-    if (body->getInfoFlags() & HEFaction)
-        attrs.append(*createAttribute(actionAtom));
-    if (body->getInfoFlags() & HEFcontextDependentException)
+    //This should mirror the code in CHqlExternalCall::CHqlExternalCall
+    unsigned impureFlags = body->getInfoFlags();
+    if (impureFlags & (HEFthrowds|HEFthrowscalar))
+        attrs.append(*createAttribute(failAtom));
+
+    if (impureFlags & HEFcontextDependentException)
         attrs.append(*createAttribute(contextSensitiveAtom));
     if (functionBodyUsesContext(body))
         attrs.append(*LINK(cachedContextAttribute));
@@ -12240,6 +12573,21 @@ IHqlExpression * createExternalFuncdefFromInternal(IHqlExpression * funcdef)
     if (child && child->getOperator()==no_embedbody)
         unwindAttribute(attrs, child, inlineAtom);
 
+    if (impureFlags & HEFcostly)
+        attrs.append(*createAttribute(costlyAtom));
+
+    if (impureFlags & HEFnoduplicate)
+        attrs.append(*createAttribute(_noDuplicate_Atom));
+
+    if (impureFlags & HEFaction)
+        attrs.append(*createAttribute(actionAtom));
+
+    if (impureFlags & HEFcontainsNlpText)
+        attrs.append(*createAttribute(userMatchFunctionAtom));
+
+    if (!(impureFlags & HEFimpure))// && attrs.empty())
+        attrs.append(*createAttribute(pureAtom));
+
     ITypeInfo * returnType = funcdef->queryType()->queryChildType();
     OwnedHqlExpr externalExpr = createExternalReference(funcdef->queryId(), LINK(returnType), attrs);
     return replaceChild(funcdef, 0, externalExpr);
@@ -15054,6 +15402,38 @@ IHqlExpression * queryOnlyField(IHqlExpression * record)
     return ret;
 }
 
+//---------------------------------------------------------------------------------------------------------------------
+
+bool canDuplicateActivity(IHqlExpression * expr)
+{
+    unsigned max = expr->numChildren();
+    for (unsigned i = getNumChildTables(expr); i < max; i++)
+    {
+        IHqlExpression * cur = expr->queryChild(i);
+        if (!canDuplicateExpr(cur))
+            return false;
+    }
+    return true;
+}
+
+bool hasTransformWithSkip(IHqlExpression * expr)
+{
+    unsigned max = expr->numChildren();
+    for (unsigned i = getNumChildTables(expr); i < max; i++)
+    {
+        IHqlExpression * cur = expr->queryChild(i);
+        if (containsSkip(cur))
+            return true;
+    }
+    return false;
+}
+
+bool isNoSkipInlineDataset(IHqlExpression * expr)
+{
+    assertex(expr->getOperator() == no_inlinetable);
+    IHqlExpression * values = expr->queryChild(0);
+    return !hasTransformWithSkip(values);
+}
 
 bool isPureActivity(IHqlExpression * expr)
 {
@@ -15103,6 +15483,8 @@ bool assignsContainSkip(IHqlExpression * expr)
     }
 }
 
+//---------------------------------------------------------------------------------------------------------------------
+
 extern HQL_API bool isKnownTransform(IHqlExpression * transform)
 {
     switch (transform->getOperator())
@@ -15157,7 +15539,7 @@ bool isContextDependent(IHqlExpression * expr, bool ignoreFailures, bool ignoreG
 
 bool isPureCanSkip(IHqlExpression * expr)
 {
-    return (expr->getInfoFlags() & (HEFvolatile|HEFaction|HEFthrowscalar|HEFthrowds)) == 0; 
+    return (expr->getInfoFlags() & (HEFnoduplicate|HEFaction|HEFthrowscalar|HEFthrowds)) == 0;
 }
 
 bool hasSideEffects(IHqlExpression * expr)
@@ -15350,10 +15732,10 @@ IHqlExpression * createSelector(node_operator op, IHqlExpression * ds, IHqlExpre
 }
 
 static UniqueSequenceCounter uidSequence;
-IHqlExpression * createUniqueId()
+IHqlExpression * createUniqueId(IAtom * name)
 {
     unsigned __int64 uid = uidSequence.next();
-    return createSequence(no_attr, NULL, _uid_Atom, uid);
+    return createSequence(no_attr, NULL, name, uid);
 }
 
 static UniqueSequenceCounter counterSequence;

+ 46 - 15
ecl/hql/hqlexpr.hpp

@@ -112,15 +112,18 @@ enum
     HEFunbound                  = 0x00000010,
     HEFinternalSelect          = 0x00000020,
     HEFcontainsDatasetAliasLocally= 0x00000040,
-  HEF____unused2____          = 0x00000080,
-  HEF____unused3____          = 0x00000100,
-    HEFfunctionOfGroupAggregate = 0x00000200,
-    HEFvolatile                 = 0x00000400,           // value changes each time called - e.g., random()
-    HEFaction                   = 0x00000800,           // an action, or something that can have a side-effect
-    HEFaccessRuntimeContext     = 0x00000100,
-    HEFthrowscalar              = 0x00002000,           // scalar/action that can throw an exception
-    HEFthrowds                  = 0x00004000,           // dataset that can throw an exception
-    HEFoldthrows                = 0x00008000,           // old throws flag, which I should remove asap
+
+//impure properties (see head of hqlexpr.cpp for detailed discussion)
+    HEFnoduplicate              = 0x00000080,           // value changes each time it is called - e.g., RANDOM()
+    HEFcontextDependentException= 0x00000100,           // depends on the context, but not known how
+    HEFcostly                   = 0x00000200,           // an expensive operation
+    HEFaction                   = 0x00000400,           // an action, or something that can have a side-effect.  Not convinced this is needed
+
+    HEFaccessRuntimeContext     = 0x00000800,
+    HEFthrowscalar              = 0x00001000,           // scalar/action that can throw an exception
+    HEFthrowds                  = 0x00002000,           // dataset that can throw an exception
+    HEFoldthrows                = 0x00004000,           // old throws flag, which I should remove asap
+    HEFfunctionOfGroupAggregate = 0x00008000,
 
 // code generator specific start from the bottom up.
 //NB: update the select 
@@ -139,7 +142,7 @@ enum
     HEFcontainsSkip             = 0x04000000,
     HEFcontainsCounter          = 0x08000000,
     HEFassertkeyed              = 0x10000000,
-    HEFcontextDependentException= 0x20000000,               // A context dependent item that doesn't fit into any other category - for use as a last resort!
+    HEF__unused5__              = 0x20000000,
     HEFcontainsAlias            = 0x40000000,
     HEFcontainsAliasLocally     = 0x80000000,
 
@@ -148,20 +151,22 @@ enum
     HEFalwaysInherit            = HEFunbound|HEFinternalSelect,
     HEFassigninheritFlags       = ~(HEFhousekeeping|HEFalwaysInherit),          // An assign inherits all but this list from the rhs value 
 
+    HEFthrow                    = (HEFthrowscalar|HEFthrowds),
 //  HEFcontextDependent         = (HEFgraphDependent|HEFcontainsNlpText|HEFcontainsXmlText|HEFcontainsSkip|HEFcontainsCounter|HEFtransformDependent|HEFtranslated|HEFonFailDependent|HEFcontextDependentException|HEFthrowscalar|HEFthrowds),
     HEFcontextDependent         = (HEFgraphDependent|HEFcontainsNlpText|HEFcontainsXmlText|HEFcontainsSkip|HEFcontainsCounter|HEFtransformDependent|HEFtranslated|HEFonFailDependent|HEFcontextDependentException|HEFoldthrows),
     HEFretainedByActiveSelect   = (HEFhousekeeping|HEFalwaysInherit),
 
     HEFintersectionFlags        = (0),
-    HEFunionFlags               = (HEFunbound|HEFfunctionOfGroupAggregate|HEFvolatile|HEFaction|HEFthrowscalar|HEFthrowds|HEFoldthrows|
+    HEFunionFlags               = (HEFunbound|HEFfunctionOfGroupAggregate|
+                                   HEFnoduplicate|HEFcontextDependentException|HEFcostly|HEFaction|HEFthrowscalar|HEFthrowds|HEFoldthrows|
                                    HEFonFailDependent|HEFcontainsActiveDataset|HEFcontainsActiveNonSelector|HEFcontainsDataset|
                                    HEFtranslated|HEFgraphDependent|HEFcontainsNlpText|HEFcontainsXmlText|HEFtransformDependent|
-                                   HEFcontainsSkip|HEFcontainsCounter|HEFassertkeyed|HEFcontextDependentException|HEFcontainsAlias|HEFcontainsAliasLocally|
+                                   HEFcontainsSkip|HEFcontainsCounter|HEFassertkeyed|HEFcontainsAlias|HEFcontainsAliasLocally|
                                    HEFinternalSelect|HEFcontainsThisNode|HEFcontainsDatasetAliasLocally|HEFaccessRuntimeContext),
 
     HEFcontextDependentNoThrow  = (HEFcontextDependent & ~(HEFthrowscalar|HEFthrowds|HEFoldthrows)),
     HEFcontextDependentDataset  = (HEFcontextDependent & ~(HEFthrowscalar)),
-    HEFimpure                   = (HEFvolatile|HEFaction|HEFthrowds|HEFthrowscalar|HEFcontainsSkip),
+    HEFimpure                   = (HEFnoduplicate|HEFaction|HEFthrowds|HEFthrowscalar|HEFcontainsSkip),
 };
 
 //NB: increase the member variable if it grows 
@@ -1505,7 +1510,8 @@ extern HQL_API unsigned unwoundCount(IHqlExpression * expr, node_operator op);
 extern HQL_API void unwindAttribute(HqlExprArray & args, IHqlExpression * expr, IAtom * name);
 extern HQL_API IHqlExpression * queryChildOperator(node_operator op, IHqlExpression * expr);
 extern HQL_API IHqlExpression * createSelector(node_operator op, IHqlExpression * ds, IHqlExpression * seq);
-extern HQL_API IHqlExpression * createUniqueId();
+extern HQL_API IHqlExpression * createUniqueId(IAtom * name);
+
 extern HQL_API IHqlExpression * createUniqueRowsId();
 extern HQL_API IHqlExpression * createCounter();
 extern HQL_API IHqlExpression * createSelectorSequence();
@@ -1527,6 +1533,9 @@ extern HQL_API IHqlExpression * ensureDeserialized(IHqlExpression * expr, ITypeI
 extern HQL_API IHqlExpression * ensureSerialized(IHqlExpression * expr, IAtom * serialForm);
 extern HQL_API bool isDummySerializeDeserialize(IHqlExpression * expr);
 
+inline IHqlExpression * createUniqueId() { return createUniqueId(_uid_Atom); }
+inline IHqlExpression * createVolatileId() { return createUniqueId(_volatileId_Atom); }
+
 extern HQL_API unsigned getRepeatMax(IHqlExpression * expr);
 extern HQL_API unsigned getRepeatMin(IHqlExpression * expr);
 extern HQL_API bool isStandardRepeat(IHqlExpression * expr);
@@ -1659,12 +1668,32 @@ extern HQL_API bool isSameUnqualifiedType(ITypeInfo * l, ITypeInfo * r);
 extern HQL_API bool isSameFullyUnqualifiedType(ITypeInfo * l, ITypeInfo * r);
 extern HQL_API IHqlExpression * queryNewSelectAttrExpr();
 
+//The following functions deal with the different aspects of impure functions - volatile,costly,throw,skip,...
+
+inline bool isVolatile(IHqlExpression * expr)           { return (expr->getInfoFlags() & HEFnoduplicate) != 0; }
+//Is it ok to duplicate the evaluation of this expression in another context?
+inline bool canDuplicateExpr(IHqlExpression * expr)      { return (expr->getInfoFlags() & (HEFnoduplicate|HEFcostly)) == 0; }
+//Is it legal to evaluate this expression in a different context - e.g, in a parent instead of child query
+inline bool canChangeContext(IHqlExpression * expr)     { return (expr->getInfoFlags() & (HEFcontextDependent|HEFthrow|HEFcontainsSkip|HEFcostly)) == 0; }
+//Is it ok to convert a conditional expression to an unconditional expression?
+inline bool canRemoveGuard(IHqlExpression * expr)       { return (expr->getInfoFlags() & (HEFthrow|HEFcontainsSkip|HEFcostly)) == 0; }
+//Is it legal to reuse the value created in another context for this expression?
+inline bool canCommonUpContext(IHqlExpression * expr)     { return (expr->getInfoFlags() & (HEFcontextDependent|HEFcontainsSkip)) == 0; }
+
+//Is it legal to duplicate this activity?
+extern HQL_API bool canDuplicateActivity(IHqlExpression * expr);
+
+extern HQL_API bool hasTransformWithSkip(IHqlExpression * expr);
+extern HQL_API bool isNoSkipInlineDataset(IHqlExpression * expr);
+
+
+
+
 //The following are wrappers for the code generator specific getInfoFlags()
 //inline bool isTableInvariant(IHqlExpression * expr)       { return (expr->getInfoFlags() & HEFtableInvariant) != 0; }
 inline bool containsActiveDataset(IHqlExpression * expr){ return (expr->getInfoFlags() & HEFcontainsActiveDataset) != 0; }
 inline bool containsActiveNonSelector(IHqlExpression * expr)
                                                         { return (expr->getInfoFlags() & HEFcontainsActiveNonSelector) != 0; }
-
 inline bool containsNonActiveDataset(IHqlExpression * expr) { return (expr->getInfoFlags() & (HEFcontainsDataset)) != 0; }
 inline bool containsAnyDataset(IHqlExpression * expr)   { return (expr->getInfoFlags() & (HEFcontainsDataset|HEFcontainsActiveDataset)) != 0; }
 inline bool containsAlias(IHqlExpression * expr)        { return (expr->getInfoFlags() & HEFcontainsAlias) != 0; }
@@ -1678,6 +1707,7 @@ inline bool containsCounter(IHqlExpression * expr)      { return (expr->getInfoF
 inline bool isCountProject(IHqlExpression * expr)       { return expr->hasAttribute(_countProject_Atom); }
 inline bool containsSkip(IHqlExpression * expr)         { return (expr->getInfoFlags() & (HEFcontainsSkip)) != 0; }
 inline bool containsSelf(IHqlExpression * expr)         { return (expr->getInfoFlags2() & (HEF2containsSelf)) != 0; }
+
 inline bool isContextDependentExceptGraph(IHqlExpression * expr)    
                                                         { return (expr->getInfoFlags() & (HEFcontextDependent & ~HEFgraphDependent)) != 0; }
 inline bool isGraphDependent(IHqlExpression * expr)     { return (expr->getInfoFlags() & HEFgraphDependent) != 0; }
@@ -1711,6 +1741,7 @@ inline IHqlExpression * queryRecord(IHqlExpression * expr)
 }
 
 extern HQL_API bool isPureVirtual(IHqlExpression * cur);
+extern HQL_API bool isVolatileFuncdef(IHqlExpression * funcdef);
 inline bool isForwardScope(IHqlScope * scope) { return scope && (queryExpression(scope)->getOperator() == no_forwardscope); }
 
 extern HQL_API bool isContextDependent(IHqlExpression * expr, bool ignoreFailures = false, bool ignoreGraph = false);

+ 8 - 4
ecl/hql/hqlfold.cpp

@@ -4764,7 +4764,11 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
                         break;
                     }
                 }
-                if (!newRow || !newRow->isPure())
+                if (!newRow)
+                    break;
+
+                //Instead of evaluating once newRow will be evaluated multiple times.  Is that ok (e.g., volatile)
+                if (!canDuplicateActivity(newRow))
                     break;
 
                 OwnedHqlExpr replacementRow = createRow(no_newrow, LINK(newRow));
@@ -4905,7 +4909,7 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
             switch (childOp)
             {
             case no_inlinetable:
-                if ((foldOptions & HFOconstantdatasets) && isPureInlineDataset(child))
+                if ((foldOptions & HFOconstantdatasets) && isNoSkipInlineDataset(child))
                     ret = queryOptimizeAggregateInline(expr, child->queryChild(0)->numChildren());
                 break;
             default:
@@ -4924,7 +4928,7 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
             switch (childOp)
             {
             case no_inlinetable:
-                if (isPureInlineDataset(child))
+                if (isNoSkipInlineDataset(child))
                     return createConstant(expr->queryType()->castFrom(false, (__int64)child->queryChild(0)->numChildren()));
                 break;
             case no_null:
@@ -4958,7 +4962,7 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
             switch (childOp)
             {
             case no_inlinetable:
-                if (isPureInlineDataset(child))
+                if (isNoSkipInlineDataset(child))
                 {
                     bool hasChildren = (child->queryChild(0)->numChildren() != 0);
                     return createConstant(hasChildren);

+ 8 - 6
ecl/hql/hqlgram.hpp

@@ -642,7 +642,7 @@ public:
     void enterScope(bool allowExternal);
     void enterVirtualScope();
     void leaveScope(const attribute & errpos);
-    IHqlExpression * leaveLamdaExpression(attribute & exprattr);
+    IHqlExpression * leaveLamdaExpression(attribute * modifierattr, attribute & exprattr);
     IHqlScope * closeLeaveScope(const YYSTYPE & errpos);
     void enterPatternScope(IHqlExpression * pattern);
     void leavePatternScope(const YYSTYPE & errpos);
@@ -710,8 +710,9 @@ public:
     void addActiveParameterOwn(const attribute & errpos, IHqlExpression * expr, IHqlExpression * defaultValue);
     void gatherActiveParameters(HqlExprCopyArray & target);
 
-
-    IHqlExpression * createUniqueId();  
+    IHqlExpression * createVolatileId() { return ::createUniqueId(_volatileId_Atom); }
+    IHqlExpression * createUniqueId() { return createUniqueId(_uid_Atom); }
+    IHqlExpression * createUniqueId(IAtom * name);
 
     void onOpenBra();
     void onCloseBra();
@@ -814,12 +815,13 @@ protected:
     IHqlExpression * createRecordExcept(IHqlExpression * left, IHqlExpression * right, const attribute & errpos);
     IHqlExpression * createIndexFromRecord(IHqlExpression * record, IHqlExpression * attr, const attribute & errpos);
     IHqlExpression * createProjectRow(attribute & rowAttr, attribute & transformAttr, attribute & seqAttr);
-    void doDefineSymbol(DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered);
-    void defineSymbolInScope(IHqlScope * scope, DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered, HqlExprArray & parameters, IHqlExpression * defaults);
+    void doDefineSymbol(DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered, IHqlExpression * modifiers);
+    void defineSymbolInScope(IHqlScope * scope, DefineIdSt * defineid, IHqlExpression * expr, const attribute & idattr, int assignPos, int semiColonPos);
     void checkDerivedCompatible(IIdAtom * name, IHqlExpression * scope, IHqlExpression * expr, bool isParametered, HqlExprArray & parameters, attribute const & errpos);
     void defineSymbolProduction(attribute & nameattr, attribute & paramattr, attribute & assignattr, attribute * valueattr, attribute * failattr, attribute & semiattr);
-    void definePatternSymbolProduction(attribute & nameattr, const attribute & assignAttr, attribute & valueAttr, attribute & workflowAttr, const attribute & semiattr);
+    void definePatternSymbolProduction(attribute & nameattr, attribute & paramattr, const attribute & assignAttr, attribute & valueAttr, attribute & workflowAttr, const attribute & semiattr);
     void cloneInheritedAttributes(IHqlScope * scope, const attribute & errpos);
+    IHqlExpression * normalizeFunctionExpression(DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, bool isParametered, HqlExprArray & parameters, IHqlExpression * defaults, IHqlExpression * modifiers);
 
     IHqlExpression * createEvaluateOutputModule(const attribute & errpos, IHqlExpression * scopeExpr, IHqlExpression * ifaceExpr, node_operator outputOp, IIdAtom *matchId);
     IHqlExpression * createStoredModule(const attribute & errpos, IHqlExpression * scopeExpr);

+ 36 - 28
ecl/hql/hqlgram.y

@@ -470,6 +470,7 @@ static void eclsyntaxerror(HqlGram * parser, const char * s, short yystate, int
   VALIDATE
   VARIANCE
   VIRTUAL
+  VOLATILE
   WAIT
   TOK_WARNING
   WHEN
@@ -1449,7 +1450,7 @@ attributeDefinition
                         }
     | definePatternIdWithOptScope parmdef featureParameters ASSIGN pattern optfailure ';'
                         {
-                            parser->definePatternSymbolProduction($1, $4, $5, $6, $7);
+                            parser->definePatternSymbolProduction($1, $2, $4, $5, $6, $7);
                             $$.clear();
                         }
     | defineFeatureIdWithOptScope ';'
@@ -1458,7 +1459,7 @@ attributeDefinition
                             IHqlExpression *expr = createValue(no_null, makeFeatureType());
                             expr = createValue(no_pat_featuredef, expr->getType(), expr);
 
-                            parser->doDefineSymbol(defineid, expr, NULL, $1, $2.pos.position, $2.pos.position, false);
+                            parser->doDefineSymbol(defineid, expr, NULL, $1, $2.pos.position, $2.pos.position, false, NULL);
                             $$.clear();
                         }
     | defineFeatureIdWithOptScope ASSIGN featureDefine ';'
@@ -1467,7 +1468,7 @@ attributeDefinition
                             IHqlExpression *expr = $3.getExpr();
                             expr = createValue(no_pat_featuredef, expr->getType(), expr);
 
-                            parser->doDefineSymbol(defineid, expr, NULL, $1, $2.pos.position, $4.pos.position, false);
+                            parser->doDefineSymbol(defineid, expr, NULL, $1, $2.pos.position, $4.pos.position, false, NULL);
                             $$.clear();
                         }
     ;
@@ -1892,7 +1893,7 @@ transform
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN transform ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -2815,7 +2816,7 @@ failAction
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN action ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     | WHEN '(' action ',' action sideEffectOptions ')'
                         {
@@ -3829,7 +3830,7 @@ eventObject
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN eventObject ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -3843,17 +3844,19 @@ event
     ;
 
 parmdef
-    : realparmdef       {   parser->setParametered(true); $$.clear(); }
-    |                   {   parser->setParametered(false); $$.clear(); }
+    : realparmdef       {   parser->setParametered(true); $$.inherit($1); }
+    |                   {   parser->setParametered(false); $$.setNullExpr(); }
     ;
 
 reqparmdef
-    : realparmdef       {   parser->setParametered(true); $$.clear(); }
+    : realparmdef       {   parser->setParametered(true); $$.inherit($1); }
     ;
 
 realparmdef
-    : '(' params ')'
-    | '(' ')'
+    : '(' params ')' functionModifiers
+                        { $$.inherit($4); }
+    | '(' ')'  functionModifiers
+                        { $$.inherit($3); }
     ;
 
 params
@@ -3987,6 +3990,11 @@ defFuncValue
     | EQ anyFunction    {   $$.setExpr($2.getExpr()); }
     ;
 
+functionModifiers
+    :                   {   $$.setNullExpr(); }
+    | VOLATILE          {   $$.setExpr(createAttribute(volatileAtom), $1); }
+    ;
+
 service
     : startService funcDefs END
                         {
@@ -4248,7 +4256,7 @@ recordDef
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN recordDef ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -5203,7 +5211,7 @@ expression
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN expression ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -5947,7 +5955,7 @@ primexpr1
                         }
     | RANDOM '(' ')'
                         {
-                            $$.setExpr(createValue(no_random, LINK(parser->uint4Type), parser->createUniqueId()));
+                            $$.setExpr(createValue(no_random, LINK(parser->uint4Type), parser->createVolatileId()));
                         }
     | ROUND '(' expression ')'
                         {
@@ -7082,7 +7090,7 @@ abstractModule
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN abstractModule ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     | IF '(' booleanExpr ',' abstractModule ',' abstractModule ')'
                         {
@@ -7304,7 +7312,7 @@ dataRow
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN dataRow ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -7806,7 +7814,7 @@ dataSet
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN dataSet ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     | startSimpleFilter conditions endSimpleFilter /* simple dataset with conditions */
                         {
@@ -11173,7 +11181,7 @@ setOfDatasets
     | startCompoundExpression beginInlineFunctionToken optDefinitions RETURN setOfDatasets ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($5), $7);
+                            $$.setExpr(parser->leaveLamdaExpression(NULL, $5), $7);
                         }
     ;
 
@@ -11339,7 +11347,7 @@ valueFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN expression ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11353,7 +11361,7 @@ actionFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN action ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11367,7 +11375,7 @@ datarowFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN dataRow ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11381,7 +11389,7 @@ datasetFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN dataSet ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11395,7 +11403,7 @@ dictionaryFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN dictionary ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11409,7 +11417,7 @@ scopeFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN abstractModule ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11424,7 +11432,7 @@ transformFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN transform ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11439,7 +11447,7 @@ recordFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN recordDef ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11453,7 +11461,7 @@ listDatasetFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN setOfDatasets ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 
@@ -11468,7 +11476,7 @@ eventFunction
     | startCompoundExpression reqparmdef beginInlineFunctionToken optDefinitions RETURN eventObject ';' endInlineFunctionToken
                         {
                             Owned<ITypeInfo> retType = $1.getType();
-                            $$.setExpr(parser->leaveLamdaExpression($6), $8);
+                            $$.setExpr(parser->leaveLamdaExpression(&$2, $6), $8);
                         }
     ;
 

+ 48 - 37
ecl/hql/hqlgram2.cpp

@@ -680,7 +680,7 @@ IHqlExpression * HqlGram::endFunctionCall()
     return ret.getClear();
 }
 
-IHqlExpression * HqlGram::createUniqueId()
+IHqlExpression * HqlGram::createUniqueId(IAtom * name)
 {
     HqlExprArray args;
     ForEachItemIn(i, defineScopes)
@@ -692,9 +692,9 @@ IHqlExpression * HqlGram::createUniqueId()
     if (args.ordinality())
     {
         args.add(*::createUniqueId(), 0);
-        return createExprAttribute(_uid_Atom, args);
+        return createExprAttribute(name, args);
     }
-    return ::createUniqueId();
+    return ::createUniqueId(name);
 }
 
 IHqlExpression * HqlGram::createActiveSelectorSequence(IHqlExpression * left, IHqlExpression * right)
@@ -1145,7 +1145,7 @@ void HqlGram::processEnum(attribute & idAttr, IHqlExpression * value)
     DefineIdSt * id = new DefineIdSt;
     id->id = idAttr.getId();
     id->scope = EXPORT_FLAG;
-    doDefineSymbol(id, LINK(lastEnumValue), NULL, idAttr, idAttr.pos.position, idAttr.pos.position, false);
+    doDefineSymbol(id, LINK(lastEnumValue), NULL, idAttr, idAttr.pos.position, idAttr.pos.position, false, NULL);
 }
 
 bool HqlGram::extractConstantString(StringBuffer & text, attribute & attr)
@@ -2832,17 +2832,18 @@ IHqlScope * HqlGram::closeLeaveScope(const YYSTYPE & errpos)
     return closeScope(scope);
 }
 
-IHqlExpression * HqlGram::leaveLamdaExpression(attribute & exprattr)
+IHqlExpression * HqlGram::leaveLamdaExpression(attribute * paramattr, attribute & exprattr)
 {
     OwnedHqlExpr resultExpr = exprattr.getExpr();
     OwnedHqlExpr expr = associateSideEffects(resultExpr, exprattr.pos);
+    OwnedHqlExpr modifiers = paramattr ? paramattr->getExpr() : NULL;
 
     if (queryParametered())
     {
         ActiveScopeInfo & activeScope = defineScopes.tos();
         OwnedHqlExpr formals = activeScope.createFormals(false);
         OwnedHqlExpr defaults = activeScope.createDefaults();
-        expr.setown(createFunctionDefinition(atId, expr.getClear(), formals.getClear(), defaults.getClear(), NULL));
+        expr.setown(createFunctionDefinition(atId, expr.getClear(), formals.getClear(), defaults.getClear(), modifiers.getClear()));
     }
 
     leaveScope(exprattr);
@@ -3774,7 +3775,7 @@ IHqlExpression* HqlGram::checkServiceDef(IHqlScope* serviceScope,IIdAtom * name,
                 cppApi = true;
                 checkSvcAttrNoValue(attr, errpos);
             }
-            else if (name == pureAtom || name == templateAtom || name == volatileAtom || name == onceAtom || name == actionAtom || name == timeAtom)
+            else if (name == pureAtom || name == templateAtom || name == volatileAtom || name == onceAtom || name == actionAtom || name == timeAtom || name == noMoveAtom || name == failAtom)
             {
                 checkSvcAttrNoValue(attr, errpos);
             }
@@ -6224,6 +6225,9 @@ IHqlExpression *HqlGram::bindParameters(const attribute & errpos, IHqlExpression
             }
             else
             {
+                //Binding an external, outofline or beginc++ function
+                if (isVolatileFuncdef(function))
+                    actuals.append(*createVolatileId());
                 bool expandCall = insideTemplateFunction() ? false : expandCallsWhenBound;
                 // do the actual binding
                 return createBoundFunction(this, function, actuals, lookupCtx.functionCache, expandCall);
@@ -9189,7 +9193,7 @@ IHqlExpression * HqlGram::associateSideEffects(IHqlExpression * expr, const ECLl
 }
 
 
-void HqlGram::doDefineSymbol(DefineIdSt * defineid, IHqlExpression * _expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered)
+void HqlGram::doDefineSymbol(DefineIdSt * defineid, IHqlExpression * _expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered, IHqlExpression * modifiers)
 {
     OwnedHqlExpr expr = _expr;
     // env symbol
@@ -9201,6 +9205,7 @@ void HqlGram::doDefineSymbol(DefineIdSt * defineid, IHqlExpression * _expr, IHql
     if (activeScope.templateAttrContext)
         expr.setown(createTemplateFunctionContext(expr.getClear(), closeScope(activeScope.templateAttrContext.getClear())));
 
+    IHqlScope * targetScope = NULL;
     if (!activeScope.localScope)
     {
         expr.setown(associateSideEffects(expr, idattr.pos));
@@ -9210,7 +9215,7 @@ void HqlGram::doDefineSymbol(DefineIdSt * defineid, IHqlExpression * _expr, IHql
             reportWarning(CategorySyntax, WRN_EXPORT_IGNORED, idattr.pos, "EXPORT/SHARED qualifiers are ignored in this context");
 
         defineid->scope = 0;
-        defineSymbolInScope(activeScope.privateScope, defineid, expr.getClear(), failure, idattr, assignPos, semiColonPos, isParametered, activeScope.activeParameters, activeScope.createDefaults());
+        targetScope = activeScope.privateScope;
     }
     else
     {
@@ -9260,14 +9265,16 @@ void HqlGram::doDefineSymbol(DefineIdSt * defineid, IHqlExpression * _expr, IHql
             //static int i = 0;
             //PrintLog("Kill private scope: %d at %s:%d because of %s", ++i, filename->str(), idattr.lineno, current_id->str());
             activeScope.newPrivateScope();
-            defineSymbolInScope(activeScope.localScope, defineid, expr.getClear(), failure, idattr, assignPos, semiColonPos, isParametered, activeScope.activeParameters, activeScope.createDefaults());
+            targetScope = activeScope.localScope;
             lastpos = semiColonPos+1;
         }
         else
         {
-            defineSymbolInScope(activeScope.privateScope, defineid, expr.getClear(), failure, idattr, assignPos, semiColonPos, isParametered, activeScope.activeParameters, activeScope.createDefaults());
+            targetScope = activeScope.privateScope;
         }
     }
+    OwnedHqlExpr normalized = normalizeFunctionExpression(defineid, expr, failure, isParametered, activeScope.activeParameters, activeScope.createDefaults(), modifiers);
+    defineSymbolInScope(targetScope, defineid, normalized, idattr, assignPos, semiColonPos);
 
     ::Release(failure);
     // clean up
@@ -9302,10 +9309,32 @@ IHqlExpression * HqlGram::attachMetaAttributes(IHqlExpression * ownedExpr, HqlEx
     return ownedExpr;
 }
 
-void HqlGram::defineSymbolInScope(IHqlScope * scope, DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, const attribute & idattr, int assignPos, int semiColonPos, bool isParametered, HqlExprArray & parameters, IHqlExpression * defaults)
+IHqlExpression * HqlGram::normalizeFunctionExpression(DefineIdSt * defineid, IHqlExpression * expr, IHqlExpression * failure, bool isParametered, HqlExprArray & parameters, IHqlExpression * defaults, IHqlExpression * modifiers)
 {
+    HqlExprCopyArray activeParameters;
+    gatherActiveParameters(activeParameters);
+    HqlExprArray meta;
+    expr = attachWorkflowOwn(meta, LINK(expr), failure, &activeParameters);
+    if (isParametered)
+    {
+        IHqlExpression * formals = createValue(no_sortlist, makeSortListType(NULL), parameters);
+        expr = createFunctionDefinition(defineid->id, expr, formals, defaults, modifiers);
+    }
+    else
+        ::Release(modifiers);
+    expr = attachPendingWarnings(expr);
+    expr = attachMetaAttributes(expr, meta);
+    IPropertyTree * doc = defineid->queryDoc();
+    if (doc)
+        expr = createJavadocAnnotation(expr, LINK(doc));
+    return expr;
+}
+
+void HqlGram::defineSymbolInScope(IHqlScope * scope, DefineIdSt * defineid, IHqlExpression * expr, const attribute & idattr, int assignPos, int semiColonPos)
+{
+    IHqlScope * exprScope = expr->queryScope();
     IHqlExpression * scopeExpr = queryExpression(scope);
-    IIdAtom * moduleName = NULL;
+    IIdAtom * moduleName = nullptr;
     if (!inType)
         moduleName = createIdAtom(scope->queryFullName());
 
@@ -9325,25 +9354,7 @@ void HqlGram::defineSymbolInScope(IHqlScope * scope, DefineIdSt * defineid, IHql
         }
     }
 
-    HqlExprCopyArray activeParameters;
-    gatherActiveParameters(activeParameters);
-
-    HqlExprArray meta;
-    expr = attachWorkflowOwn(meta, expr, failure, &activeParameters);
-    if (isParametered)
-    {
-        IHqlExpression * formals = createValue(no_sortlist, makeSortListType(NULL), parameters);
-        expr = createFunctionDefinition(defineid->id, expr, formals, defaults, NULL);
-    }
-
-    expr = attachPendingWarnings(expr);
-    expr = attachMetaAttributes(expr, meta);
-
-    IPropertyTree * doc = defineid->queryDoc();
-    if (doc)
-        expr = createJavadocAnnotation(expr, LINK(doc));
-
-    scope->defineSymbol(defineid->id, moduleName, expr, (defineid->scope & EXPORT_FLAG) != 0, (defineid->scope & SHARED_FLAG) != 0, symbolFlags, lexObject->query_FileContents(), idattr.pos.lineno, idattr.pos.column, idattr.pos.position, assignPos+2, semiColonPos+1);
+    scope->defineSymbol(defineid->id, moduleName, LINK(expr), (defineid->scope & EXPORT_FLAG) != 0, (defineid->scope & SHARED_FLAG) != 0, symbolFlags, lexObject->query_FileContents(), idattr.pos.lineno, idattr.pos.column, idattr.pos.position, assignPos+2, semiColonPos+1);
 }
 
 
@@ -9520,7 +9531,7 @@ void HqlGram::defineSymbolProduction(attribute & nameattr, attribute & paramattr
                     {
                         if (!matchType->assignableFrom(etype))
                         {
-                            canNotAssignTypeError(type, etype, paramattr);
+                            canNotAssignTypeError(type, etype, nameattr);
                             expr.setown(createNullExpr(matchType));
                         }
                     }
@@ -9540,7 +9551,7 @@ void HqlGram::defineSymbolProduction(attribute & nameattr, attribute & paramattr
             {
                 if (queryRecord(type) != queryNullRecord())
                 {
-                    canNotAssignTypeError(type,etype,paramattr);
+                    canNotAssignTypeError(type,etype,nameattr);
                     switch (type->getTypeCode())
                     {
                     case type_record:
@@ -9572,11 +9583,11 @@ void HqlGram::defineSymbolProduction(attribute & nameattr, attribute & paramattr
     }
 
     // env symbol
-    doDefineSymbol(defineid, expr.getClear(), failure, nameattr, assignattr.pos.position, semiattr.pos.position, activeScope.isParametered);
+    doDefineSymbol(defineid, expr.getClear(), failure, nameattr, assignattr.pos.position, semiattr.pos.position, activeScope.isParametered, paramattr.getExpr());
 }
 
 
-void HqlGram::definePatternSymbolProduction(attribute & nameattr, const attribute & assignAttr, attribute & valueAttr, attribute & workflowAttr, const attribute & semiattr)
+void HqlGram::definePatternSymbolProduction(attribute & nameattr, attribute & paramattr, const attribute & assignAttr, attribute & valueAttr, attribute & workflowAttr, const attribute & semiattr)
 {
     DefineIdSt* defineid = nameattr.getDefineId();
 
@@ -9623,7 +9634,7 @@ void HqlGram::definePatternSymbolProduction(attribute & nameattr, const attribut
             args.append(*createAttribute(_function_Atom));
         expr = createValue(no_pat_instance, expr->getType(), args);
     }
-    doDefineSymbol(defineid, expr, failure, nameattr, assignAttr.pos.position, semiattr.pos.position, queryParametered());
+    doDefineSymbol(defineid, expr, failure, nameattr, assignAttr.pos.position, semiattr.pos.position, queryParametered(), paramattr.getExpr());
 }
 
 //-- SAS style conditional assignments

+ 2 - 1
ecl/hql/hqllex.l

@@ -964,6 +964,7 @@ UPDATE              { RETURNSYM(UPDATE); }
 USE                 { RETURNSYM(USE); }
 VALIDATE            { RETURNSYM(VALIDATE); }
 VIRTUAL             { RETURNSYM(VIRTUAL); }
+VOLATILE            { RETURNSYM(VOLATILE); }
 WAIT                { RETURNSYM(WAIT); }
 WARNING             { RETURNSYM(TOK_WARNING); }
 WHEN                { RETURNSYM(WHEN); }
@@ -1993,4 +1994,4 @@ void HqlLex::doEnterEmbeddedMode(yyscan_t yyscanner)
 {
    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
    BEGIN(CPP);
-}
+}

+ 7 - 4
ecl/hql/hqlmeta.cpp

@@ -714,6 +714,11 @@ bool isKnownDistribution(IHqlExpression * distribution)
     return distribution && (distribution != queryUnknownAttribute());
 }
 
+bool isKnownNonVolatileDistribution(IHqlExpression * distribution)
+{
+    return isKnownDistribution(distribution) && !isVolatile(distribution);
+}
+
 bool isSortedDistribution(IHqlExpression * distribution)
 {
     return distribution && (distribution->queryName() == sortedAtom);
@@ -830,7 +835,6 @@ extern HQL_API IHqlExpression * mapJoinDistribution(TableProjectMapper & mapper,
     return NULL;
 }
 
-
 extern HQL_API IHqlExpression * mapDistribution(IHqlExpression * distribution, TableProjectMapper & mapper)
 {
     if (!distribution) 
@@ -1314,7 +1318,7 @@ static bool includesFieldsOutsideGrouping(IHqlExpression * distribution, const H
 bool isPartitionedForGroup(IHqlExpression * table, IHqlExpression *grouping, bool isGroupAll)
 {
     IHqlExpression * distribution = queryDistribution(table);
-    if (!isKnownDistribution(distribution) || !distribution->isPure())
+    if (!isKnownNonVolatileDistribution(distribution))
         return false;
 
     OwnedHqlExpr normalizedGrouping = normalizeSortlist(grouping, table);
@@ -1790,8 +1794,7 @@ bool isDistributedCoLocally(IHqlExpression * dataset1, IHqlExpression * dataset2
     IHqlExpression * distribute2 = queryDistribution(dataset2);
     //Check the distribution functions are equivalent - by walking through in parallel, and don't contain any
     //references to fields not in the join conditions
-    if (isKnownDistribution(distribute1) && distribute1->isPure() &&
-        isKnownDistribution(distribute2) && distribute2->isPure())
+    if (isKnownNonVolatileDistribution(distribute1) && isKnownNonVolatileDistribution(distribute2))
     {
         //If sorted they are only going to be codistributed if they came from the same sort
         //We could only determine that by making the sort orders unique - by appending a uid

+ 3 - 2
ecl/hql/hqlthql.cpp

@@ -711,6 +711,7 @@ void HqltHql::toECL(IHqlExpression *expr, StringBuffer &s, bool paren, bool inTy
 #ifdef SHOWCONTEXTDETAIL
             s.append('[');
             unsigned flags = expr->getInfoFlags();
+            if (flags & HEFnoduplicate) s.append('V');
             if (flags & HEFgraphDependent) s.append('G');
             if (flags & HEFcontainsSkip) s.append('S');
             if (flags & HEFcontainsCounter) s.append('C');
@@ -1455,7 +1456,7 @@ void HqltHql::toECL(IHqlExpression *expr, StringBuffer &s, bool paren, bool inTy
                 for (unsigned idx = 0; idx < kids; idx++)
                 {
                     IHqlExpression *child = expr->queryChild(idx);
-                    if (!child->isAttribute())
+                    if (expandProcessed || !child->isAttribute())
                     {
                         if (!first)
                             s.append(", ");
@@ -1561,7 +1562,7 @@ void HqltHql::toECL(IHqlExpression *expr, StringBuffer &s, bool paren, bool inTy
             bool first = true;
             while(IHqlExpression *kid = expr->queryChild(idx))
             {
-                bool isHidden = false;
+                bool isHidden = !expandProcessed && kid->isAttribute();
                 if (formals && !expandProcessed)
                 {
                     IHqlExpression *formal = formals->queryChild(idx);

+ 4 - 0
ecl/hql/hqlutil.cpp

@@ -5807,6 +5807,10 @@ IHqlExpression * extractCppBodyAttrs(unsigned lenBuffer, const char * buffer)
                     i = end;
                     if(matchOption(start, end, buffer, 4, "pure", false, valueStart, valueEnd))
                         attrs.setown(createComma(attrs.getClear(), createAttribute(pureAtom)));
+                    else if (matchOption(start, end, buffer, 8, "volatile", false, valueStart, valueEnd))
+                        attrs.setown(createComma(attrs.getClear(), createAttribute(volatileAtom)));
+                    else if (matchOption(start, end, buffer, 4, "costly", false, valueStart, valueEnd))
+                        attrs.setown(createComma(attrs.getClear(), createAttribute(costlyAtom)));
                     else if (matchOption(start, end, buffer, 4, "once", false, valueStart, valueEnd))
                         attrs.setown(createComma(attrs.getClear(), createAttribute(onceAtom)));
                     else if (matchOption(start, end, buffer, 6, "action", false, valueStart, valueEnd))

+ 3 - 1
ecl/hqlcpp/hqlcppcase.cpp

@@ -799,7 +799,9 @@ void HqlCppCaseInfo::buildGeneralReturn(BuildCtx & ctx)
 
 bool HqlCppCaseInfo::okToAlwaysEvaluateDefault()
 {
-    return defaultValue->isPure();
+    if (!canRemoveGuard(defaultValue))
+        return false;
+    return true;
 }
 
 ITypeInfo * HqlCppCaseInfo::queryCompareType()

+ 6 - 1
ecl/hqlcpp/hqlcse.cpp

@@ -465,7 +465,10 @@ bool CseSpotter::checkPotentialCSE(IHqlExpression * expr, CseSpotterInfo * extra
     if (extra->alreadyAliased)
         return false;
 
-    if (!expr->isPure() || !canCreateTemporary(expr))
+    if (!expr->isPure())
+        return false;
+
+    if (!canCreateTemporary(expr))
         return false;
 
     if (invariantSelector && exprReferencesDataset(expr, invariantSelector))
@@ -1223,10 +1226,12 @@ static bool canHoistInvariant(IHqlExpression * expr)
         if ((expr->getOperator() != no_alias) || expr->hasAttribute(globalAtom))
             return false;
     }
+
     if (!expr->isPure())
         return false;
     if (expr->isFunction())
         return false;
+
     switch (expr->getOperator())
     {
     case no_list:

+ 1 - 1
ecl/hqlcpp/hqlhtcpp.cpp

@@ -385,7 +385,7 @@ public:
             if (moveTo->isUnconditional() && isUsedUnconditionallyEnough(expr))
                 guard.set(queryBoolExpr(true));
 
-            bool invalid = !guard->isPure();
+            bool invalid = !canDuplicateExpr(guard);
             //version 1: don't guard any child queries.
             if (!matchesBoolean(guard, true))
             {

+ 2 - 2
ecl/hqlcpp/hqliproj.cpp

@@ -575,7 +575,7 @@ void UsedFieldSet::gatherTransformValuesUsed(HqlExprArray * selfSelects, HqlExpr
             IHqlExpression * transformValue = queryTransformAssignValue(transform, field);
             assertex(transformValue);
             bool includeThis = true;
-            if (!curNested.includeAll() && transformValue->isPure())
+            if (!curNested.includeAll() && !containsSkip(transformValue))
             {
                 if (transformValue->getOperator() == no_createrow)
                 {
@@ -2289,7 +2289,7 @@ void ImplicitProjectTransformer::processTransform(ComplexImplicitProjectInfo * e
         case no_assign:
             {
                 IHqlExpression * value = cur->queryChild(1);
-                if (!value->isPure())
+                if (containsSkip(value))
                 {
                     IHqlExpression * lhs = cur->queryChild(0);
                     processMatchingSelector(extra->outputFields, lhs, lhs->queryChild(0));

+ 4 - 1
ecl/hqlcpp/hqlresource.cpp

@@ -3091,6 +3091,9 @@ bool ResourcerInfo::expandRatherThanSpill(bool noteOtherSpills)
         if (info && info->forceHoist)
             return false;
 
+        if (!canDuplicateActivity(expr))
+            return false;
+
         node_operator op = expr->getOperator();
         switch (op)
         {
@@ -3644,7 +3647,7 @@ static bool isPotentialCompoundSteppedIndexRead(IHqlExpression * expr)
         case no_choosen:
             {
                 IHqlExpression * arg2 = expr->queryChild(2);
-                if (arg2 && !arg2->isPure())
+                if (arg2 && !canDuplicateExpr(arg2))
                     return false;
                 break;
             }

+ 12 - 6
ecl/hqlcpp/hqlwcpp.cpp

@@ -1191,22 +1191,28 @@ StringBuffer & HqlCppWriter::generateExprCpp(IHqlExpression * expr)
                 else
                     out.append(str(funcdef->queryBody()->queryId()));
                 out.append('(');
+
+                bool needComma = false;
                 if (functionBodyUsesContext(props))
                 {
                     out.append("ctx");
-                    if (numArgs)
-                        out.append(',');
+                    needComma = true;
                 }
                 else if (props->hasAttribute(globalContextAtom))
                 {
                     out.append("gctx");
-                    if (numArgs)
-                        out.append(',');
+                    needComma = true;
                 }
                 for (unsigned index = firstArg; index < numArgs; index++)
                 {
-                    if (index != firstArg) out.append(',');
-                    generateExprCpp(expr->queryChild(index));
+                    IHqlExpression * cur = expr->queryChild(index);
+                    if (!cur->isAttribute())
+                    {
+                        if (needComma)
+                            out.append(',');
+                        needComma = true;
+                        generateExprCpp(cur);
+                    }
                 }
                 out.append(')');
                 break;

+ 1 - 1
ecl/regress/volatile.ecl

@@ -36,7 +36,7 @@ now := Debug.msTick();
 output(startTime*startTime-now*now);
 
 
-nowTime() := define Debug.msTick();
+nowTime() volatile := define Debug.msTick();
 
 //Evaluate nowTime twice
 output(startTime*startTime-nowTime()*nowTime());

+ 50 - 0
ecl/regress/volatile1.ecl

@@ -0,0 +1,50 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+
+// The following assume RANDOM() aren't strictly correct, but assume it is unlikely for RANDOM() to return the
+// same number twice in succession.
+
+//Random should be re-evaluated for each unique instance.
+output(IF(random() != random(), 'Pass', 'Fail'));
+
+//There is only a single instance of this variable - it should not be re-evaluated
+volatile1 := random();
+output(IF(volatile1 = volatile1, 'Pass', 'Fail'));
+
+//Again, re-evaluating the function should not create a new value
+volatile2() := random();
+output(IF(volatile2() = volatile2(), 'Pass', 'Fail'));
+
+//Again, no reason for the random in the function to be re-evaluated.
+volatile3(integer x) := random() % x;
+output(IF(volatile3(100) % 50 = volatile3(50), 'Pass', 'Fail'));
+
+//Explicitly create a unique volatile instance for each call instance - even if the same parameters
+volatile4(integer n) volatile := random();
+output(IF(volatile4(100) != volatile4(100), 'Pass', 'Fail'));
+output(IF(volatile4(100) != volatile4(99), 'Pass', 'Fail'));
+
+//Create a unique instance for each value of n
+volatile5(integer n) := volatile4(n);
+output(IF(volatile5(1) != volatile5(2), 'Pass', 'Fail'));
+output(IF(volatile5(5) = volatile5(5), 'Pass', 'Fail'));
+
+//Create a unique volatile instance each time the function is called.
+volatile6() volatile := random() % 100;
+output(IF(volatile6() != volatile6(), 'Pass', 'Fail'));

+ 53 - 0
ecl/regress/volatile2.ecl

@@ -0,0 +1,53 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+rtl := SERVICE
+ unsigned4 rtlRandom() : eclrtl,volatile,library='eclrtl',entrypoint='rtlRandom';
+END;
+
+// The following assume RANDOM() aren't strictly correct, but assume it is unlikely for RANDOM() to return the
+// same number twice in succession.
+
+//Random should be re-evaluated for each unique instance.
+output(IF(rtl.rtlRandom() != rtl.rtlRandom(), 'Pass', 'Fail'));
+
+//There is only a single instance of this variable - it should not be re-evaluated
+volatile1 := rtl.rtlRandom();
+output(IF(volatile1 = volatile1, 'Pass', 'Fail'));
+
+//Again, re-evaluating the function should not create a new value
+volatile2() := rtl.rtlRandom();
+output(IF(volatile2() = volatile2(), 'Pass', 'Fail'));
+
+//Again, no reason for the random in the function to be re-evaluated.
+volatile3(integer x) := rtl.rtlRandom() % x;
+output(IF(volatile3(100) % 50 = volatile3(50), 'Pass', 'Fail'));
+
+//Explicitly create a unique volatile instance for each call instance - even if the same parameters
+volatile4(integer n) volatile := rtl.rtlRandom();
+output(IF(volatile4(100) != volatile4(100), 'Pass', 'Fail'));
+output(IF(volatile4(100) != volatile4(99), 'Pass', 'Fail'));
+
+//Create a unique instance for each value of n
+volatile5(integer n) := volatile4(n);
+output(IF(volatile5(1) != volatile5(2), 'Pass', 'Fail'));
+output(IF(volatile5(5) = volatile5(5), 'Pass', 'Fail'));
+
+//Create a unique volatile instance each time the function is called.
+volatile6() volatile := rtl.rtlRandom() % 100;
+output(IF(volatile6() != volatile6(), 'Pass', 'Fail'));

+ 52 - 0
ecl/regress/volatile3.ecl

@@ -0,0 +1,52 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+//Should be re-evaluated for each unique call.
+output(IF(nextSequence() != nextSequence(), 'Pass', 'Fail'));
+
+//There is only a single instance of this variable - it should not be re-evaluated
+volatile1 := nextSequence();
+output(IF(volatile1 = volatile1, 'Pass', 'Fail'));
+
+//Again, re-evaluating the function should not create a new value
+volatile2() := nextSequence();
+output(IF(volatile2() = volatile2(), 'Pass', 'Fail'));
+
+//Again, no reason for the random in the function to be re-evaluated.
+volatile3(integer x) := nextSequence() % x;
+output(IF(volatile3(100) % 50 = volatile3(50), 'Pass', 'Fail'));
+
+//Explicitly create a unique volatile instance for each call instance - even if the same parameters
+volatile4(integer n) volatile := nextSequence();
+output(IF(volatile4(100) != volatile4(100), 'Pass', 'Fail'));
+output(IF(volatile4(100) != volatile4(99), 'Pass', 'Fail'));
+
+//Create a unique instance for each value of n
+volatile5(integer n) := volatile4(n);
+output(IF(volatile5(1) != volatile5(2), 'Pass', 'Fail'));
+output(IF(volatile5(5) = volatile5(5), 'Pass', 'Fail'));
+
+//Create a unique volatile instance each time the function is called.
+volatile6() volatile := nextSequence() % 100;
+output(IF(volatile6() != volatile6(), 'Pass', 'Fail'));

+ 41 - 0
ecl/regress/volatile4.ecl

@@ -0,0 +1,41 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+#option ('targetClusterType','hthor');
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+ds := dataset(10, transform({unsigned id}, SELF.id := nextSequence()));
+
+//Check the call to nextSequence isn't evaluated outside the dataset
+min1 := min(ds, id);
+output(IF(min1 = 1, 'Pass', 'Fail'));
+
+//Check the dataset isn't ev-evaluated
+min2 := min(ds, id-1);
+output(IF(min2 = 0, 'Pass', 'Fail'));
+
+max1 := max(ds, id);
+output(IF(max1 = 10, 'Pass', 'Fail'));
+
+//Check selecting a value from the dataset also doesn't re-evaluate the value.
+output(IF(ds[5].id = 5, 'Pass', 'Fail'));

+ 54 - 0
ecl/regress/volatile5.ecl

@@ -0,0 +1,54 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+ds1 := dataset(10, transform({unsigned id}, SELF.id := random());
+ds2 := dataset(random(), transform({unsigned id}, SELF.id := random());
+
+//A call to random() should be volatile
+output(IF(__IS__(random(), volatile), 'Pass', 'Fail'));
+
+//A call to a function containing a volatile is volatile - even if it doesn't create a new instance
+myValue() := random();
+output(IF(__IS__(myValue(), volatile), 'Pass', 'Fail'));
+
+//An output of a volatile value is not volatile
+o1 := output(random());
+output(IF(NOT __IS__(o1, volatile), 'Pass', 'Fail'));
+
+//A dataset with a volatile inside a transform is not itself volatile
+output(IF(NOT __IS__(ds1, volatile), 'Pass', 'Fail'));
+
+//But a volatile used in another context is - a volatile count
+output(IF(__IS__(ds2, volatile), 'Pass', 'Fail'));
+
+//or a volatile filter is also volatile.
+output(IF(__IS__(ds1(id = RANDOM(), volatile), 'Pass', 'Fail'));
+
+//Does this make sense - moving a filter over a project could possibly make the whole dataset volatile when it wasn't before.
+ds3 = DATASET(10, TRANSFORM({unsigned id}, SELF.id := COUNTER));
+ds4 := PROJECT(NOFOLD(ds3), TRANSFORM({unsigned id}, SELF.id := RANDOM());
+ds5 := ds4(id != 10);
+output(IF(NOT __IS__(ds5, volatile), 'Pass', 'Fail'));
+
+//An aggregate of a volatile value isn't volatile
+v1 := max(ds1, id * RANDOM());
+output(IF(NOT __IS__(v1, volatile), 'Pass', 'Fail'));
+
+//But of a dataset is.
+v2 := max(ds2, id);
+output(IF(__IS__(v2, volatile), 'Pass', 'Fail'));

+ 42 - 0
ecl/regress/volatile6a1.ecl

@@ -0,0 +1,42 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 1
+// used within a child query, and not globally, therefore, each transform call should
+// re-evaluate the child datset, since the default is not to move the dataset outside.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds;
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result1 := TABLE(nofold(p), { minId := MIN(child, id) });
+output(count(result1(minId = 1)) = 1);

+ 42 - 0
ecl/regress/volatile6a2.ecl

@@ -0,0 +1,42 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 1
+// used within a child query, and not globally, therefore, each transform call should
+// re-evaluate the child datset, since the default is not to move the dataset outside.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds;
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result2 := TABLE(p, { minId := MIN(child, id) });
+output(count(result2(minId = 1)) = 1);

+ 42 - 0
ecl/regress/volatile6b1.ecl

@@ -0,0 +1,42 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 2
+// used within a child query, but the dataset is marked as global.
+// Therefore hoist the child datset and use the common value each time.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds WITHIN {};
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result1 := TABLE(nofold(p), { minId := MIN(child, id) });
+output(count(result1(minId = 1)) = 100);

+ 42 - 0
ecl/regress/volatile6b2.ecl

@@ -0,0 +1,42 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 2
+// used within a child query, but the dataset is marked as global.
+// Therefore hoist the child datset and use the common value each time.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds WITHIN {};
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result2 := TABLE(p, { minId := MIN(child, id) });
+output(count(result2(minId = 1)) = 100);

+ 43 - 0
ecl/regress/volatile6c1.ecl

@@ -0,0 +1,43 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 3
+// used within a child query, but also globally.
+// each transform call should re-evaluate the child datset, since the default is not to move the dataset outside.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds;
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result1 := TABLE(nofold(p), { minId := MIN(child, id) });
+output(count(nofold(ds)) = 10);
+output(count(result1(minId = 1)) = 1);

+ 43 - 0
ecl/regress/volatile6c2.ecl

@@ -0,0 +1,43 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+//instance 3
+// used within a child query, but also globally.
+// each transform call should re-evaluate the child datset, since the default is not to move the dataset outside.
+
+ds2 := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER));
+outRec := { unsigned id, dataset(r) child };
+outRec t(ds2 l) := TRANSFORM
+    SELF.child := ds;
+    SELF.id := l.id;
+END;
+
+p := PROJECT(NOFOLD(ds2), t(LEFT));
+
+result2 := TABLE(p, { minId := MIN(child, id) });
+output(count(nofold(ds)) = 10);
+output(count(result2(minId = 1)) = 1);

+ 41 - 0
ecl/regress/volatile7a.ecl

@@ -0,0 +1,41 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+r2 := {DATASET(R) children};
+
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+ds2(unsigned base) := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER + base));
+
+r2 t1(r l) := TRANSFORM
+    value := RANDOM();
+    self.children := ds2(value + l.id);
+END;
+
+p := PROJECT(ds, t1(LEFT));
+
+summary := TABLE(NOFOLD(p), { unsigned delta := MAX(children, id) - MIN(children, id); });
+
+output(count(summary(delta = 99)) != 10);

+ 41 - 0
ecl/regress/volatile7b.ecl

@@ -0,0 +1,41 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2016 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/>.
+############################################################################## */
+
+unsigned4 nextSequence() := BEGINC++
+#option volatile
+static unsigned mySequence = 0;
+return ++mySequence;
+ENDC++;
+
+r := {unsigned id};
+r2 := {DATASET(R) children};
+
+ds := dataset(10, transform(r, SELF.id := nextSequence()));
+
+ds2(unsigned base) := DATASET(100, transform({ unsigned id }, SELF.id := COUNTER + base));
+
+r2 t1(r l) := TRANSFORM
+    value := RANDOM() WITHIN {l};
+    self.children := ds2(value + l.id);
+END;
+
+p := PROJECT(ds, t1(LEFT));
+
+summary := TABLE(NOFOLD(p), { unsigned delta := MAX(children, id) - MIN(children, id); });
+
+output(count(summary(delta = 99)) = 10);

+ 1 - 1
ecl/regress/volatileds.ecl

@@ -1,6 +1,6 @@
 /*##############################################################################
 
-    Copyright (C) 2011 HPCC Systems®.
+    HPCC SYSTEMS software Copyright (C) 2016 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

+ 1 - 1
ecllibrary/std/system/Debug.ecl

@@ -6,7 +6,7 @@
 
 rtl := SERVICE
  unsigned4 msTick() :       eclrtl,library='eclrtl',entrypoint='rtlTick';
- unsigned4 sleep(unsigned4 _delay) : eclrtl,library='eclrtl',entrypoint='rtlSleep';
+ unsigned4 sleep(unsigned4 _delay) : eclrtl,action,library='eclrtl',entrypoint='rtlSleep';
 END;
 
 import lib_parselib;