瀏覽代碼

HPCC-10541 Add a COUNTER to JOIN

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 11 年之前
父節點
當前提交
12a71c8b23

+ 8 - 4
ecl/hql/hqlgram.y

@@ -7787,14 +7787,15 @@ simpleDataSet
                             $$.setExpr(createDataset(no_distribute, $3.getExpr(), value.getClear()));
                             $$.setPosition($1);
                         }
-    | JOIN '(' startLeftDelaySeqFilter ',' startRightFilter ',' expression opt_join_transform_flags ')' endSelectorSequence
+    | JOIN '(' startLeftDelaySeqFilter ',' startRightFilter ',' expression beginCounterScope opt_join_transform_flags endCounterScope ')' endSelectorSequence
                         {
                             parser->normalizeExpression($7, type_boolean, false);
 
                             IHqlExpression * left = $3.getExpr();
                             IHqlExpression * right = $5.getExpr();
                             IHqlExpression * cond = $7.getExpr();
-                            OwnedHqlExpr flags = $8.getExpr();
+                            OwnedHqlExpr flags = $9.getExpr();
+                            OwnedHqlExpr counter = $10.getExpr();
                             OwnedHqlExpr transform;
                             if (flags)
                             {
@@ -7813,11 +7814,14 @@ simpleDataSet
 
                             if (!transform)
                             {
-                                IHqlExpression * seq = $10.queryExpr();
+                                IHqlExpression * seq = $12.queryExpr();
                                 transform.setown(parser->createDefJoinTransform(left,right,$1,seq,flags));
                             }
 
-                            IHqlExpression *join = createDataset(no_join, left, createComma(right, cond, createComma(transform.getClear(), flags.getClear(), $10.getExpr())));
+                            if (counter)
+                                flags.setown(createComma(flags.getClear(), createAttribute(_countProject_Atom, counter.getClear())));
+
+                            IHqlExpression *join = createDataset(no_join, left, createComma(right, cond, createComma(transform.getClear(), flags.getClear(), $12.getExpr())));
 
                             bool isLocal = join->hasAttribute(localAtom);
                             parser->checkDistribution($3, left, isLocal, true);

+ 2 - 2
ecl/hql/hqlopt.cpp

@@ -2996,7 +2996,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
 #endif
                 break;
             case no_selfjoin:
-                if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom))
+                if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom) && !child->hasAttribute(_countProject_Atom))
                 {
                     //Strictly speaking, we could hoist conditions that can be hoisted for left only (or even full) joins etc. if the fields that are filtered
                     //are based on equalities in the join condition.  However, that can wait....  (same for join below...)
@@ -3012,7 +3012,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
                 }
                 break;
             case no_join:
-                if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom))
+                if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom) && !child->hasAttribute(_countProject_Atom))
                 {
                     bool canHoistLeft = !child->hasAttribute(rightouterAtom) && !child->hasAttribute(rightonlyAtom);
                     bool canMergeLeft = isInnerJoin(child);

+ 0 - 4
ecl/hqlcpp/hqlckey.cpp

@@ -578,10 +578,6 @@ void KeyedJoinInfo::buildTransform(BuildCtx & ctx)
     switch (expr->getOperator())
     {
     case no_join:
-        {
-            funcctx.addQuotedCompound("virtual size32_t transform(ARowBuilder & crSelf, const void * _left, const void * _right, unsigned __int64 _filepos)");
-            break;
-        }
     case no_denormalize:
         {
             funcctx.addQuotedCompound("virtual size32_t transform(ARowBuilder & crSelf, const void * _left, const void * _right, unsigned __int64 _filepos, unsigned counter)");

+ 2 - 8
ecl/hqlcpp/hqlhtcpp.cpp

@@ -12082,6 +12082,8 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
     BuildCtx transformctx(instance->startctx);
     switch (op)
     {
+    case no_join:
+    case no_selfjoin:
     case no_denormalize:
         {
             transformctx.addQuotedCompound("virtual size32_t transform(ARowBuilder & crSelf, const void * _left, const void * _right, unsigned counter)");
@@ -12104,14 +12106,6 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
             doBuildTransformBody(transformctx, transform, selfCursor);
             break;
         }
-    case no_join:
-    case no_selfjoin:
-        {
-            transformctx.addQuotedCompound("virtual size32_t transform(ARowBuilder & crSelf, const void * _left, const void * _right)");
-            ensureRowAllocated(transformctx, "crSelf");
-            buildTransformBody(transformctx, transform, dataset1, dataset2, instance->dataset, selSeq);
-            break;
-        }
     }
 
     IHqlExpression * onFail = expr->queryAttribute(onFailAtom);

+ 37 - 23
ecl/hthor/hthor.cpp

@@ -4275,6 +4275,7 @@ void CHThorJoinActivity::ready()
     }
 
     rightIndex = 0;
+    joinCounter = 0;
     failingLimit.clear();
     state = JSfill;
     if ((helper.getJoinFlags() & JFlimitedprefixjoin) && helper.getJoinLimit()) 
@@ -4340,6 +4341,7 @@ void CHThorJoinActivity::fillLeft()
     if (limitedhelper && 0==rightIndex)
     {
         rightIndex = 0;
+        joinCounter = 0;
         right.clear();
         matchedRight.kill();
         if (left)
@@ -4367,6 +4369,7 @@ void CHThorJoinActivity::fillRight()
     else
         right.clear();
     rightIndex = 0;
+    joinCounter = 0;
     unsigned groupCount = 0;
     while(true)
     {
@@ -4446,12 +4449,12 @@ void CHThorJoinActivity::fillRight()
         matchedRight.append(false);
 }
 
-const void * CHThorJoinActivity::joinRecords(const void * curLeft, const void * curRight)
+const void * CHThorJoinActivity::joinRecords(const void * curLeft, const void * curRight, unsigned counter)
 {
     try
     {
         outBuilder.ensureRow();
-        size32_t thisSize = helper.transform(outBuilder, curLeft, curRight);
+        size32_t thisSize = helper.transform(outBuilder, curLeft, curRight, counter);
         if(thisSize)
             return outBuilder.finalizeRowClear(thisSize);
         else
@@ -4602,14 +4605,15 @@ const void *CHThorJoinActivity::nextInGroup()
                             if (!matchedRight.item(rightIndex))
                             {
                                 const void * rhs = right.item(rightIndex++);
-                                const void * ret = joinRecords(defaultLeft, rhs);
+                                const void * ret = joinRecords(defaultLeft, rhs, 0);
                                 if (ret)
                                 {
                                     processed++;
                                     return ret;
                                 }
                             }
-                            rightIndex++;
+                            else
+                                rightIndex++;
                         }
                         break;
                     }
@@ -4687,7 +4691,7 @@ const void *CHThorJoinActivity::nextInGroup()
                 switch (kind)
                 {
                 case TAKjoin:
-                    ret = joinRecords(left, defaultRight);
+                    ret = joinRecords(left, defaultRight, 0);
                     break;
                 case TAKdenormalize:
                     ret = left.getClear();
@@ -4726,7 +4730,7 @@ const void *CHThorJoinActivity::nextInGroup()
                                 matchedLeft = true;
                                 if (!exclude)
                                 {
-                                    const void *ret = joinRecords(left, rhs);
+                                    const void *ret = joinRecords(left, rhs, ++joinCounter);
                                     if (ret)
                                     {
                                         processed++;
@@ -4817,6 +4821,7 @@ const void *CHThorJoinActivity::nextInGroup()
             }
             state = JSleftonly;
             rightIndex = 0;
+            joinCounter = 0;
             break;
         }
     }
@@ -4896,6 +4901,7 @@ void CHThorSelfJoinActivity::ready()
         limitedhelper.setown(createRHLimitedCompareHelper());
         limitedhelper->init( helper.getJoinLimit(), dualcache->queryOut2(), collate, helper.queryPrefixCompare() );
     }
+    joinCounter = 0;
 }
 
 void CHThorSelfJoinActivity::done()
@@ -4976,6 +4982,7 @@ bool CHThorSelfJoinActivity::fillGroup()
     }
     leftIndex = 0;
     rightIndex = 0;
+    joinCounter = 0;
     rightOuterIndex = 0;
     joinLimit = keepLimit;
     ForEachItemIn(idx, group)
@@ -4994,6 +5001,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
                 if (lhs)
                 {
                     rightIndex = 0;
+                    joinCounter = 0;
                     group.clear();
                     limitedhelper->getGroup(group,lhs);
                 }
@@ -5006,7 +5014,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
                 const void * rhs = group.item(rightIndex++);
                 if(helper.match(lhs, rhs))
                 {
-                    const void * ret = joinRecords(lhs, rhs);
+                    const void * ret = joinRecords(lhs, rhs, ++joinCounter, NULL);
                     return ret;
                 }
             }
@@ -5024,7 +5032,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
         if(failingOuterAtmost)
             while(group.isItem(leftIndex))
             {
-                const void * ret = joinRecords(group.item(leftIndex++), defaultRight);
+                const void * ret = joinRecords(group.item(leftIndex++), defaultRight, 0, NULL);
                 if(ret)
                 {
                     processed++;
@@ -5035,7 +5043,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
         {
             if(leftOuterJoin && !matchedLeft && !failingLimit)
             {
-                const void * ret = joinRecords(group.item(leftIndex), defaultRight);
+                const void * ret = joinRecords(group.item(leftIndex), defaultRight, 0, NULL);
                 if(ret)
                 {
                     matchedLeft = true;
@@ -5046,6 +5054,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
             leftIndex++;
             matchedLeft = false;
             rightIndex = 0;
+            joinCounter = 0;
             joinLimit = keepLimit;
         }
         if(!group.isItem(leftIndex))
@@ -5055,7 +5064,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
                 OwnedConstRoxieRow lhs(input->nextInGroup());
                 while(lhs)
                 {
-                    const void * ret = joinRecords(lhs, defaultRight, failingLimit);
+                    const void * ret = joinRecords(lhs, defaultRight, 0, failingLimit);
                     if(ret)
                     {
                         processed++;
@@ -5069,7 +5078,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
                 while(group.isItem(rightOuterIndex))
                     if(!matchedRight.item(rightOuterIndex++))
                     {
-                        const void * ret = joinRecords(defaultLeft, group.item(rightOuterIndex-1));
+                        const void * ret = joinRecords(defaultLeft, group.item(rightOuterIndex-1), 0, NULL);
                         if(ret)
                         {
                             processed++;
@@ -5084,7 +5093,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
         if(failingLimit)
         {
             leftIndex++;
-            const void * ret = joinRecords(lhs, defaultRight, failingLimit);
+            const void * ret = joinRecords(lhs, defaultRight, 0, failingLimit);
             if(ret)
             {
                 processed++;
@@ -5100,7 +5109,7 @@ const void * CHThorSelfJoinActivity::nextInGroup()
                 matchedRight.replace(true, rightIndex-1);
                 if(!exclude)
                 {
-                    const void * ret = joinRecords(lhs, rhs);
+                    const void * ret = joinRecords(lhs, rhs, ++joinCounter, NULL);
                     if(ret)
                     {
                         processed++;
@@ -5114,12 +5123,12 @@ const void * CHThorSelfJoinActivity::nextInGroup()
     return NULL;
 }
 
-const void * CHThorSelfJoinActivity::joinRecords(const void * curLeft, const void * curRight, IException * except)
+const void * CHThorSelfJoinActivity::joinRecords(const void * curLeft, const void * curRight, unsigned counter, IException * except)
 {
     outBuilder.ensureRow();
     try
     {
-            size32_t thisSize = (except ? helper.onFailTransform(outBuilder, curLeft, curRight, except) : helper.transform(outBuilder, curLeft, curRight));
+            size32_t thisSize = (except ? helper.onFailTransform(outBuilder, curLeft, curRight, except) : helper.transform(outBuilder, curLeft, curRight, counter));
             if(thisSize){
                 return outBuilder.finalizeRowClear(thisSize);   
             }
@@ -5258,6 +5267,7 @@ void CHThorLookupJoinActivity::ready()
         createDefaultRight();   
     eog = false;
     matchedGroup = false;
+    joinCounter = 0;
 }
 
 void CHThorLookupJoinActivity::done()
@@ -5313,12 +5323,12 @@ void CHThorLookupJoinActivity::setInput(unsigned index, IHThorInput * _input)
 }
 
 //following are all copied from CHThorJoinActivity - should common up.
-const void * CHThorLookupJoinActivity::joinRecords(const void * left, const void * right)
+const void * CHThorLookupJoinActivity::joinRecords(const void * left, const void * right, unsigned counter)
 {
     try
     {
         outBuilder.ensureRow();
-        size32_t thisSize = helper.transform(outBuilder, left, right);
+        size32_t thisSize = helper.transform(outBuilder, left, right, counter);
         if(thisSize)
             return outBuilder.finalizeRowClear(thisSize);
         else
@@ -5430,7 +5440,7 @@ const void * CHThorLookupJoinActivity::nextInGroupJoin()
                     gotMatch = true;
                     if(exclude)
                         break;
-                    ret = joinRecords(left, right);
+                    ret = joinRecords(left, right, ++joinCounter);
                     if(ret)
                         break;
                 }
@@ -5439,7 +5449,7 @@ const void * CHThorLookupJoinActivity::nextInGroupJoin()
             }
             if(leftOuterJoin && !gotMatch)
             {
-                ret = joinRecords(left, defaultRight);
+                ret = joinRecords(left, defaultRight, 0);
                 gotMatch = true;
             }
         }
@@ -5450,11 +5460,13 @@ const void * CHThorLookupJoinActivity::nextInGroupJoin()
             if(!many || (--keepCount == 0) || failingLimit)
             {
                 left.clear();
+                joinCounter = 0;
                 failingLimit.clear();
             }
             return ret;
         }
         left.clear();
+        joinCounter = 0;
     }
 }
 
@@ -5681,15 +5693,16 @@ void CHThorAllJoinActivity::loadRight()
         matchedRight.append(false);
     }
     rightIndex = 0;
+    joinCounter = 0;
     rightOrdinality = rightset.ordinality();
 }
 
-const void * CHThorAllJoinActivity::joinRecords(const void * left, const void * right)
+const void * CHThorAllJoinActivity::joinRecords(const void * left, const void * right, unsigned counter)
 {
     try
     {
         outBuilder.ensureRow();
-        memsize_t thisSize = helper.transform(outBuilder, left, right);
+        memsize_t thisSize = helper.transform(outBuilder, left, right, counter);
         if(thisSize)
             return outBuilder.finalizeRowClear(thisSize);
         else
@@ -5763,7 +5776,7 @@ const void * CHThorAllJoinActivity::nextInGroup()
                 switch(kind)
                 {
                 case TAKalljoin:
-                    ret = joinRecords(left, defaultRight);
+                    ret = joinRecords(left, defaultRight, 0);
                     break;
                 case TAKalldenormalize:
                     ret = left.getClear();
@@ -5777,6 +5790,7 @@ const void * CHThorAllJoinActivity::nextInGroup()
                 }
             }
             rightIndex = 0;
+            joinCounter = 0;
             left.clear();
             if(ret)
             {
@@ -5822,7 +5836,7 @@ const void * CHThorAllJoinActivity::nextInGroup()
                     matchedLeft = true;
                     matchedRight.replace(true, rightIndex);
                     if(!exclude)
-                        ret = joinRecords(left, right);
+                        ret = joinRecords(left, right, ++joinCounter);
                 }
                 rightIndex++;
                 if(ret)

+ 8 - 4
ecl/hthor/hthor.ipp

@@ -1227,6 +1227,7 @@ class CHThorJoinActivity : public CHThorActivityBase
     OwnedConstRoxieRow left;
     OwnedConstRoxieRow pendingRight;
     unsigned rightIndex;
+    unsigned joinCounter;
     BoolArray matchedRight;
     bool matchedLeft;
     Owned<IException> failingLimit;
@@ -1247,7 +1248,7 @@ private:
     void fillRight();
     //bool getMatchingRecords();
     //bool queryAdvanceCursors();
-    const void * joinRecords(const void * curLeft, const void * curRight);
+    const void * joinRecords(const void * curLeft, const void * curRight, unsigned counter);
     const void * groupDenormalizeRecords(const void * curLeft, ConstPointerArray & rows);
     const void * joinException(const void * curLeft, IException * except);
     void failLimit();
@@ -1297,6 +1298,7 @@ class CHThorSelfJoinActivity : public CHThorActivityBase
     unsigned leftIndex;
     unsigned rightIndex;
     unsigned rightOuterIndex;
+    unsigned joinCounter;
     bool eof;
     bool doneFirstFill;
 
@@ -1311,7 +1313,7 @@ class CHThorSelfJoinActivity : public CHThorActivityBase
     Owned<CRHDualCache> dualcache;
 private:
     bool fillGroup();
-    const void * joinRecords(const void * curLeft, const void * curRight, IException * except = NULL);
+    const void * joinRecords(const void * curLeft, const void * curRight, unsigned counter, IException * except);
     void failLimit(const void * next);
 
 public:
@@ -1368,6 +1370,7 @@ private:
     unsigned keepLimit;
     unsigned atmostLimit;
     unsigned limitLimit;
+    unsigned joinCounter;
     bool limitFail;
     bool limitOnFail;
     bool hasGroupLimit;
@@ -1390,7 +1393,7 @@ private:
 private:
     void loadRight();
     const void * groupDenormalizeRecords(const void * curLeft, ConstPointerArray & rows);
-    const void * joinRecords(const void * left, const void * right);
+    const void * joinRecords(const void * left, const void * right, unsigned counter);
     const void * joinException(const void * left, IException * except);
     const void * getRightFirst() { if(hasGroupLimit) return fillRightGroup(); else return table->find(left); }
     const void * getRightNext() { if(hasGroupLimit) return readRightGroup(); else return table->findNext(left); }
@@ -1445,6 +1448,7 @@ private:
     unsigned countForLeft;
     unsigned rightIndex;
     unsigned rightOrdinality;
+    unsigned joinCounter;
     OwnedRowArray rightset;
     ConstPointerArray filteredRight;
     BoolArray matchedRight;
@@ -1452,7 +1456,7 @@ private:
 
 private:
     void loadRight();
-    const void * joinRecords(const void * left, const void * right);
+    const void * joinRecords(const void * left, const void * right, unsigned counter);
     const void * groupDenormalizeRecords(const void * left, ConstPointerArray & rows);
     void createDefaultRight();  
 public:

+ 3 - 2
ecl/hthor/hthorkey.cpp

@@ -3710,7 +3710,7 @@ public:
                         {
                             RtlDynamicRowBuilder rowBuilder(rowAllocator);
                             if (kind == TAKkeyedjoin)
-                                transformedSize = helper.transform(rowBuilder, left, defaultRight, 0);
+                                transformedSize = helper.transform(rowBuilder, left, defaultRight, (__uint64)0, (unsigned)0);
                             else if (kind == TAKkeyeddenormalizegroup)
                                 transformedSize = helper.transform(rowBuilder, left, defaultRight, 0, (const void * *)NULL);
                             if (transformedSize)
@@ -3750,6 +3750,7 @@ public:
                 {
                     if(jg->matches.start())
                     {
+                        unsigned counter = 0;
                         do
                         {
                             try
@@ -3759,7 +3760,7 @@ public:
                                 if(!row) continue;
                                 offset_t fpos = jg->matches.queryOffset();
                                 size32_t transformedSize;
-                                transformedSize = helper.transform(rowBuilder, left, row, fpos);
+                                transformedSize = helper.transform(rowBuilder, left, row, fpos, ++counter);
                                 if (transformedSize)
                                 {
                                     const void * shrunk = rowBuilder.finalizeRowClear(transformedSize);

+ 53 - 30
roxie/ccd/ccdserver.cpp

@@ -11644,6 +11644,7 @@ class CRoxieServerJoinActivity : public CRoxieServerTwoInputActivity
     const void * left;
     const void * pendingRight;
     unsigned rightIndex;
+    unsigned joinCounter;
     BoolArray matchedRight;
     bool matchedLeft;
     Owned<IException> failingLimit;
@@ -11706,6 +11707,7 @@ public:
             collate = collateupper = helper.queryCompareLeftRight();
         }
         rightIndex = 0;
+        joinCounter = 0;
         state = JSfill;
         matchedLeft = false;
         joinLimit = 0;
@@ -11725,6 +11727,7 @@ public:
     {
         left = NULL;
         rightIndex = 0;
+        joinCounter = 0;
         state = JSfill;
         matchedLeft = false;
 
@@ -11805,6 +11808,7 @@ public:
         if (limitedhelper && 0==rightIndex)
         {
             rightIndex = 0;
+            joinCounter = 0;
             right.clear();
             matchedRight.kill();
             if (left)
@@ -11832,6 +11836,7 @@ public:
         else
             right.clear();
         rightIndex = 0;
+        joinCounter = 0;
         unsigned groupCount = 0;
         const void * next;
         while(true)
@@ -11916,7 +11921,7 @@ public:
             matchedRight.append(false);
     }
 
-    const void * joinRecords(const void * curLeft, const void * curRight)
+    const void * joinRecords(const void * curLeft, const void * curRight, unsigned counter)
     {
         if (cloneLeft)
         {
@@ -11926,7 +11931,7 @@ public:
         try
         {
             RtlDynamicRowBuilder rowBuilder(rowAllocator);
-            size32_t thisSize = helper.transform(rowBuilder, curLeft, curRight);
+            size32_t thisSize = helper.transform(rowBuilder, curLeft, curRight, counter);
             if (thisSize)
                 return rowBuilder.finalizeRowClear(thisSize);
             else
@@ -12065,14 +12070,15 @@ public:
                                 if (!matchedRight.item(rightIndex))
                                 {
                                     const void * rhs = right.item(rightIndex++);
-                                    const void *ret = joinRecords(defaultLeft, rhs);
+                                    const void *ret = joinRecords(defaultLeft, rhs, 0);
                                     if (ret)
                                     {
                                         processed++;
                                         return ret;
                                     }
                                 }
-                                rightIndex++;
+                                else
+                                    rightIndex++;
                             }
                             break;
                         }
@@ -12149,7 +12155,7 @@ public:
                     switch (activityKind)
                     {
                     case TAKjoin:
-                        ret = joinRecords(left, defaultRight);
+                        ret = joinRecords(left, defaultRight, 0);
                         break;
                     case TAKdenormalize:
                         ret = left;
@@ -12188,7 +12194,7 @@ public:
                                     matchedLeft = true;
                                     if (!exclude)
                                     {
-                                        const void *ret = joinRecords(left, rhs);
+                                        const void *ret = joinRecords(left, rhs, ++joinCounter);
                                         if (ret)
                                         {
                                             processed++;
@@ -12277,6 +12283,7 @@ public:
                 }
                 state = JSleftonly;
                 rightIndex = 0;
+                joinCounter = 0;
                 break;
             }
         }
@@ -16863,6 +16870,7 @@ class CRoxieServerSelfJoinActivity : public CRoxieServerActivity
     unsigned atmostsTriggered;
     unsigned abortLimit;
     unsigned keepLimit;
+    unsigned joinCounter;
     bool leftOuterJoin;
     bool rightOuterJoin;
     bool exclude;
@@ -16954,6 +16962,7 @@ class CRoxieServerSelfJoinActivity : public CRoxieServerActivity
         leftIndex = 0;
         rightIndex = 0;
         rightOuterIndex = 0;
+        joinCounter = 0;
         joinLimit = keepLimit;
         ForEachItemIn(idx, group)
             matchedRight.append(false);
@@ -16973,7 +16982,7 @@ class CRoxieServerSelfJoinActivity : public CRoxieServerActivity
 
     virtual bool needsAllocator() const { return true; }
 
-    const void *joinRecords(const void * curLeft, const void * curRight, IException * except = NULL)
+    const void *joinRecords(const void * curLeft, const void * curRight, unsigned counter, IException * except)
     {
         try
         {
@@ -16983,7 +16992,7 @@ class CRoxieServerSelfJoinActivity : public CRoxieServerActivity
                 return curLeft;
             }
             RtlDynamicRowBuilder rowBuilder(rowAllocator);
-            size32_t outsize = except ? helper.onFailTransform(rowBuilder, curLeft, curRight, except) : helper.transform(rowBuilder, curLeft, curRight);
+            size32_t outsize = except ? helper.onFailTransform(rowBuilder, curLeft, curRight, except) : helper.transform(rowBuilder, curLeft, curRight, counter);
             if (outsize)
                 return rowBuilder.finalizeRowClear(outsize);
             else
@@ -17047,6 +17056,7 @@ public:
         leftIndex = 0;
         rightIndex = 0;
         rightOuterIndex = 0;
+        joinCounter = 0;
         dualCacheInput = NULL;
     }
 
@@ -17081,6 +17091,7 @@ public:
             matchedLeft = false;
             leftIndex = 0;
             rightOuterIndex = 0;
+            joinCounter = 0;
 
             limitedhelper.setown(createRHLimitedCompareHelper());
             limitedhelper->init( helper.getJoinLimit(), dualcache->queryOut2(), collate, helper.queryPrefixCompare() );
@@ -17110,6 +17121,7 @@ public:
                     if (lhs)
                     {
                         rightIndex = 0;
+                        joinCounter = 0;
                         group.clear();
                         limitedhelper->getGroup(group,lhs);
                     }
@@ -17124,7 +17136,7 @@ public:
                     const void * rhs = group.item(rightIndex++);
                     if(helper.match(lhs, rhs))
                     {
-                        const void * ret = joinRecords(lhs, rhs);
+                        const void * ret = joinRecords(lhs, rhs, ++joinCounter, NULL);
                         return ret;
                     }
                 }
@@ -17143,7 +17155,7 @@ public:
                 if(failingOuterAtmost)
                     while(group.isItem(leftIndex))
                     {
-                        const void * ret = joinRecords(group.item(leftIndex++), defaultRight);
+                        const void * ret = joinRecords(group.item(leftIndex++), defaultRight, 0, NULL);
                         if(ret)
                         {
                             processed++;
@@ -17154,7 +17166,7 @@ public:
                 {
                     if(leftOuterJoin && !matchedLeft && !failingLimit)
                     {
-                        const void * ret = joinRecords(group.item(leftIndex), defaultRight);
+                        const void * ret = joinRecords(group.item(leftIndex), defaultRight, 0, NULL);
                         if(ret)
                         {
                             matchedLeft = true;
@@ -17165,6 +17177,7 @@ public:
                     leftIndex++;
                     matchedLeft = false;
                     rightIndex = 0;
+                    joinCounter = 0;
                     joinLimit = keepLimit;
                 }
                 if(!group.isItem(leftIndex))
@@ -17174,7 +17187,7 @@ public:
                         const void * lhs;
                         while((lhs = input->nextInGroup()) != NULL)  // dualCache never active here
                         {
-                            const void * ret = joinRecords(lhs, defaultRight, failingLimit);
+                            const void * ret = joinRecords(lhs, defaultRight, 0, failingLimit);
                             ReleaseRoxieRow(lhs);
                             if(ret)
                             {
@@ -17188,7 +17201,7 @@ public:
                         while(group.isItem(rightOuterIndex))
                             if(!matchedRight.item(rightOuterIndex++))
                             {
-                                const void * ret = joinRecords(defaultLeft, group.item(rightOuterIndex-1));
+                                const void * ret = joinRecords(defaultLeft, group.item(rightOuterIndex-1), 0, NULL);
                                 if(ret)
                                 {
                                     processed++;
@@ -17203,7 +17216,7 @@ public:
                 if(failingLimit)
                 {
                     leftIndex++;
-                    const void * ret = joinRecords(lhs, defaultRight, failingLimit);
+                    const void * ret = joinRecords(lhs, defaultRight, 0, failingLimit);
                     if(ret)
                     {
                         processed++;
@@ -17219,7 +17232,7 @@ public:
                         matchedRight.replace(true, rightIndex-1);
                         if(!exclude)
                         {
-                            const void * ret = joinRecords(lhs, rhs);
+                            const void * ret = joinRecords(lhs, rhs, ++joinCounter, NULL);
                             if(ret)
                             {
                                 processed++;
@@ -17535,6 +17548,7 @@ private:
     unsigned atmostLimit;
     unsigned atmostsTriggered;
     unsigned limitLimit;
+    unsigned joinCounter;
     bool limitFail;
     bool limitOnFail;
     bool hasGroupLimit;
@@ -17580,6 +17594,7 @@ public:
         gotMatch = false;
         keepLimit = 0;
         keepCount = 0;
+        joinCounter = 0;
         atmostLimit = 0;
         atmostsTriggered = 0;
         limitLimit = 0;
@@ -17626,7 +17641,7 @@ public:
                             void **_rows = const_cast<void * *>(rightset.getArray());
                             memcpy(temp, _rows, rightord*sizeof(void **));
                             qsortvecstable(temp, rightord, *helper.queryCompareRight(), (void ***)_rows);
-                            for (int i = 0; i < rightord; i++)
+                            for (unsigned i = 0; i < rightord; i++)
                             {
                                 *_rows = **((void ***)_rows);
                                 _rows++;
@@ -17737,6 +17752,7 @@ private:
             {
                 left = input->nextInGroup();
                 keepCount = keepLimit;
+                joinCounter = 0;
                 if(!left)
                 {
                     if (isSmartJoin)
@@ -17774,7 +17790,7 @@ private:
                         gotMatch = true;
                         if(exclude)
                             break;
-                        ret = joinRecords(left, right);
+                        ret = joinRecords(left, right, ++joinCounter);
                         if(ret)
                             break;
                     }
@@ -17783,7 +17799,7 @@ private:
                 }
                 if(leftOuterJoin && !gotMatch)
                 {
-                    ret = joinRecords(left, defaultRight);
+                    ret = joinRecords(left, defaultRight, 0);
                     gotMatch = true;
                 }
             }
@@ -17900,7 +17916,7 @@ private:
         }
     }
 
-    const void * joinRecords(const void * left, const void * right)
+    const void * joinRecords(const void * left, const void * right, unsigned counter)
     {
         if (cloneLeft)
         {
@@ -17910,7 +17926,7 @@ private:
         try
         {
             RtlDynamicRowBuilder rowBuilder(rowAllocator);
-            unsigned outSize = helper.transform(rowBuilder, left, right);
+            unsigned outSize = helper.transform(rowBuilder, left, right, counter);
             if (outSize)
                 return rowBuilder.finalizeRowClear(outSize);
             else
@@ -18071,6 +18087,7 @@ private:
     bool leftIsGrouped;
     bool cloneLeft;
     unsigned rightIndex;
+    unsigned joinCounter;
     unsigned rightOrdinality;
     ThorActivityKind activityKind;
     ConstPointerArray filteredRight;
@@ -18122,6 +18139,7 @@ public:
         rightOrdinality = 0;
         leftIsGrouped = false;
         countForLeft = 0;
+        joinCounter = 0;
     }
 
     virtual void reset()
@@ -18149,6 +18167,7 @@ public:
         if(keepLimit==0)
             keepLimit = (unsigned) -1;
         countForLeft = keepLimit;
+        joinCounter = 0;
         leftIsGrouped = input->queryOutputMeta()->isGrouped();
         if((activityKind==TAKalljoin || activityKind==TAKalldenormalizegroup) && leftOuterJoin)
             createDefaultRight();
@@ -18170,6 +18189,7 @@ public:
             matchedRight.append(false);
         }
         rightIndex = 0;
+        joinCounter = 0;
         rightOrdinality = rightset.ordinality();
     }
 
@@ -18189,7 +18209,7 @@ public:
         }
     }
 
-    const void * joinRecords(const void * left, const void * right)
+    const void * joinRecords(const void * left, const void * right, unsigned counter)
     {
         // MORE - could share some code with lookup join
         if (cloneLeft)
@@ -18200,7 +18220,7 @@ public:
         try
         {
             RtlDynamicRowBuilder rowBuilder(rowAllocator);
-            unsigned outSize = helper.transform(rowBuilder, left, right);
+            unsigned outSize = helper.transform(rowBuilder, left, right, counter);
             if (outSize)
                 return rowBuilder.finalizeRowClear(outSize);
             else
@@ -18240,6 +18260,7 @@ public:
             left = input->nextInGroup();
             matchedLeft = false;
             countForLeft = keepLimit;
+            joinCounter = 0;
             if(left == NULL)
             {
                 eos = true;
@@ -18264,7 +18285,7 @@ public:
                     switch(activityKind)
                     {
                     case TAKalljoin:
-                        ret = joinRecords(left, defaultRight);
+                        ret = joinRecords(left, defaultRight, 0);
                         break;
                     case TAKalldenormalize:
                         ret = left;
@@ -18279,6 +18300,7 @@ public:
                     }
                 }
                 rightIndex = 0;
+                joinCounter = 0;
                 ReleaseRoxieRow(left);
                 left = NULL;
                 if(ret)
@@ -18294,6 +18316,7 @@ public:
                 left = input->nextInGroup();
                 matchedLeft = false;
                 countForLeft = keepLimit;
+                joinCounter = 0;
             }
             if(!left)
             {
@@ -18325,7 +18348,7 @@ public:
                         matchedLeft = true;
                         matchedRight.replace(true, rightIndex);
                         if(!exclude)
-                            ret = joinRecords(left, right);
+                            ret = joinRecords(left, right, ++joinCounter);
                     }
                     rightIndex++;
                     if(ret)
@@ -24422,7 +24445,7 @@ public:
 
     virtual bool needsAllocator() const { return true; }
 
-    unsigned doTransform(const void *left, const void *right, offset_t fpos_or_count, IException *except, const void **group)
+    unsigned doTransform(const void *left, const void *right, offset_t fpos_or_count, IException *except, const void **group, unsigned counter)
     {
         if (cloneLeft && !except)
         {
@@ -24437,7 +24460,7 @@ public:
         {   
             outSize = except ? helper.onFailTransform(rowBuilder, left, right, fpos_or_count, except) : 
                       (activityKind == TAKkeyeddenormalizegroup) ? helper.transform(rowBuilder, left, right, (unsigned) fpos_or_count, group) : 
-                      helper.transform(rowBuilder, left, right, fpos_or_count);
+                      helper.transform(rowBuilder, left, right, fpos_or_count, counter);
         }
         catch (IException *E)
         {
@@ -24476,7 +24499,7 @@ public:
                 {
                     except.setown(e);
                 }
-                added = doTransform(left, defaultRight, 0, except, NULL);
+                added = doTransform(left, defaultRight, 0, except, NULL, 0);
             }
         }
         else if (!matched || jg->candidateCount() > atMost)
@@ -24491,7 +24514,7 @@ public:
                 {
                 case TAKkeyedjoin:
                 case TAKkeyeddenormalizegroup:
-                    added = doTransform(left, defaultRight, 0, NULL, NULL);
+                    added = doTransform(left, defaultRight, 0, NULL, NULL, 0);
                     break;
                 case TAKkeyeddenormalize:
                     LinkRoxieRow(left);
@@ -24511,7 +24534,7 @@ public:
                 while (idx < matched)
                 {
                     const KeyedJoinHeader *rhs = jg->queryRow(idx);
-                    added += doTransform(left, &rhs->rhsdata, rhs->fpos, NULL, NULL);
+                    added += doTransform(left, &rhs->rhsdata, rhs->fpos, NULL, NULL, idx+1);
                     if (added==keepLimit)
                         break;
                     idx++;
@@ -24561,7 +24584,7 @@ public:
                         extractedRows.append((void *) &rhs->rhsdata);
                         idx++;
                     }
-                    added += doTransform(left, extractedRows.item(0), extractedRows.ordinality(), NULL, (const void * *)extractedRows.getArray());
+                    added += doTransform(left, extractedRows.item(0), extractedRows.ordinality(), NULL, (const void * *)extractedRows.getArray(), 0);
                 }
                 break;
             }

+ 2 - 4
rtl/include/eclhelper.hpp

@@ -39,8 +39,8 @@ if the supplied pointer was not from the roxiemem heap. Usually an OwnedRoxieStr
 
 //Should be incremented whenever the virtuals in the context or a helper are changed, so
 //that a work unit can't be rerun.  Try as hard as possible to retain compatibility.
-#define ACTIVITY_INTERFACE_VERSION      154
-#define MIN_ACTIVITY_INTERFACE_VERSION  154             //minimum value that is compatible with current interface - without using selectInterface
+#define ACTIVITY_INTERFACE_VERSION      155
+#define MIN_ACTIVITY_INTERFACE_VERSION  155             //minimum value that is compatible with current interface - without using selectInterface
 
 typedef unsigned char byte;
 
@@ -1666,7 +1666,6 @@ struct IHThorAnyJoinBaseArg : public IHThorArg
     virtual unsigned getKeepLimit() = 0;
 
 //Join:
-    virtual size32_t transform(ARowBuilder & rowBuilder, const void * _left, const void * _right) { return 0; }
 //Denormalize
     virtual size32_t transform(ARowBuilder & rowBuilder, const void * _left, const void * _right, unsigned _count) { return 0; }
 //Denormalize group
@@ -1747,7 +1746,6 @@ struct IHThorKeyedJoinBaseArg : public IHThorArg
 
     virtual size32_t onFailTransform(ARowBuilder & rowBuilder, const void * _dummyRight, const void * _origRow, unsigned __int64 keyedFpos, IException * e) { return 0; }
 //Join:
-    virtual size32_t transform(ARowBuilder & rowBuilder, const void * _joinFields, const void * _origRow, unsigned __int64 keyedFpos) { return 0; }
 //Denormalize:
     virtual size32_t transform(ARowBuilder & rowBuilder, const void * _joinFields, const void * _origRow, unsigned __int64 keyedFpos, unsigned counter) { return 0; }
 //Denormalize group:

+ 57 - 53
testing/regress/ecl/joinattr2.ecl

@@ -25,6 +25,7 @@ END;
 RECORD
         string1 idL;
         string1 idR;
+        unsigned cnt;
 END;
 
  
@@ -40,69 +41,72 @@ ds8 := DATASET([{1,'A'}, {1,'B'}, {1,'C'}], rec);
 ds9 := DATASET([{2,'A'}], rec);
 ds10 := DATASET([{0,'A'}], rec);
 
-recout trans(rec L, rec R) :=
+recout trans(rec L, rec R, unsigned cnt) :=
 TRANSFORM
 
             SELF.idl := L.id;
             SELF.idr := R.id;
+            SELF.cnt := cnt;
 END;
 
-recout transkip(rec L, rec R) :=
+recout transkip(rec L, rec R, unsigned cnt) :=
 TRANSFORM
 
             SELF.idl := if(L.id='A',skip,L.id);
             SELF.idr := if(R.id='A',skip,R.id);
+            SELF.cnt := cnt;
 END;
 
-j1 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), KEEP(1), LEFT OUTER);
-j2 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), ATMOST(1), LEFT OUTER);
-j3 := JOIN(ds2, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), RIGHT OUTER);
-j4 := JOIN(ds1, ds7, (LEFT.i = RIGHT.i), transkip(LEFT, RIGHT));
-j5 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), LEFT OUTER);
-j6 := JOIN(ds2, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), RIGHT OUTER);
-j7 := JOIN(ds4, ds3, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT), KEEP(2));
-j8 := JOIN(ds1, ds3, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), KEEP(2),LEFT OUTER);
-j9 := JOIN(ds6, ds7, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), KEEP(5));
-j10 := JOIN(ds6, ds7, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), RIGHT OUTER);
-j11 := JOIN(ds1, ds7, (LEFT.i = RIGHT.i), transkip(LEFT, RIGHT));
-j12 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), RIGHT OUTER);
-j13 := JOIN(ds1, ds3, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), RIGHT OUTER);
-j14 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT), KEEP(2));
-j15 := JOIN(ds1, ds1, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), KEEP(1));
-j16 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), KEEP(1));
-j17 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), LOOKUP);
-j18 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), ATMOST(1));
-j19 := JOIN(ds7, ds6, (LEFT.i = RIGHT.i)and(LEFT.id<=RIGHT.id), trans(LEFT, RIGHT), LEFT OUTER, LOOKUP);
-j20 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), KEEP(1), LEFT OUTER);
-j21 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j22 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), ATMOST(1), LEFT OUTER);
-j23 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), RIGHT OUTER);
-j24 := JOIN(ds1, ds9, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT), KEEP(2));
-j25 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), KEEP(1), LEFT OUTER);
-j26 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j27 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), ATMOST(1), LEFT OUTER);
-j28 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), RIGHT OUTER);
-j29 := JOIN(ds9, ds1, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT), KEEP(2));
-j30 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), KEEP(1), LEFT OUTER);
-j31 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), LOOKUP);
-j32 := JOIN(ds1, ds1, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), LOOKUP);
-j33 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, trans(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j34 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j35 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT), LOOKUP);
-j36 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j37 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j38 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), KEEP(1), LEFT OUTER);
-j39 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), LOOKUP, LEFT OUTER);
-j40 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT), ATMOST(1), LEFT OUTER);
-
-j41 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), MANY LOOKUP);
-j42 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), MANY LOOKUP, KEEP(1));
-j43 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), MANY LOOKUP, ATMOST(1));
-j44 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT), MANY LOOKUP, LEFT OUTER);
-j45 := JOIN(ds1, ds8, RIGHT.id<'B', trans(LEFT, RIGHT), ALL);
-j46 := JOIN(ds1, ds8, RIGHT.id<'B', trans(LEFT, RIGHT), ALL, KEEP(1));
-j47 := JOIN(ds1, ds8, RIGHT.id>'E', trans(LEFT, RIGHT), ALL, LEFT OUTER);
-j48 := JOIN(ds1, ds8, LEFT.i = RIGHT.i AND RIGHT.id>'B', trans(LEFT, RIGHT), LOOKUP);
+j1 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), KEEP(1), LEFT OUTER);
+j2 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), ATMOST(1), LEFT OUTER);
+j3 := JOIN(ds2, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j4 := JOIN(ds1, ds7, (LEFT.i = RIGHT.i), transkip(LEFT, RIGHT, COUNTER));
+j5 := JOIN(ds1, ds2, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), LEFT OUTER);
+j6 := JOIN(ds2, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j7 := JOIN(ds4, ds3, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT, COUNTER), KEEP(2));
+j8 := JOIN(ds1, ds3, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), KEEP(2),LEFT OUTER);
+j9 := JOIN(ds6, ds7, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), KEEP(5));
+j10 := JOIN(ds6, ds7, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j11 := JOIN(ds1, ds7, (LEFT.i = RIGHT.i), transkip(LEFT, RIGHT, COUNTER));
+j12 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j13 := JOIN(ds1, ds3, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j14 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT, COUNTER), KEEP(2));
+j15 := JOIN(ds1, ds1, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), KEEP(1));
+j16 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), KEEP(1));
+j17 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), LOOKUP);
+j18 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), ATMOST(1));
+j19 := JOIN(ds7, ds6, (LEFT.i = RIGHT.i)and(LEFT.id<=RIGHT.id), trans(LEFT, RIGHT, COUNTER), LEFT OUTER, LOOKUP);
+j20 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), KEEP(1), LEFT OUTER);
+j21 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j22 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), ATMOST(1), LEFT OUTER);
+j23 := JOIN(ds1, ds9, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j24 := JOIN(ds1, ds9, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT, COUNTER), KEEP(2));
+j25 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), KEEP(1), LEFT OUTER);
+j26 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j27 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), ATMOST(1), LEFT OUTER);
+j28 := JOIN(ds9, ds1, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), RIGHT OUTER);
+j29 := JOIN(ds9, ds1, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), trans(LEFT, RIGHT, COUNTER), KEEP(2));
+j30 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), KEEP(1), LEFT OUTER);
+j31 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), LOOKUP);
+j32 := JOIN(ds1, ds1, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), LOOKUP);
+j33 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, trans(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j34 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j35 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT, COUNTER), LOOKUP);
+j36 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i)and(RIGHT.id>'E'), transkip(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j37 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j38 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), KEEP(1), LEFT OUTER);
+j39 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), LOOKUP, LEFT OUTER);
+j40 := JOIN(ds1, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), ATMOST(1), LEFT OUTER);
+
+j41 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), MANY LOOKUP);
+j42 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), MANY LOOKUP, KEEP(1));
+j43 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), MANY LOOKUP, ATMOST(1));
+j44 := JOIN(ds1, ds8, (LEFT.i = RIGHT.i), trans(LEFT, RIGHT, COUNTER), MANY LOOKUP, LEFT OUTER);
+j45 := JOIN(ds1, ds8, RIGHT.id<'B', trans(LEFT, RIGHT, COUNTER), ALL);
+j46 := JOIN(ds1, ds8, RIGHT.id<'B', trans(LEFT, RIGHT, COUNTER), ALL, KEEP(1));
+j47 := JOIN(ds1, ds8, RIGHT.id>'E', trans(LEFT, RIGHT, COUNTER), ALL, LEFT OUTER);
+j48 := JOIN(ds1, ds8, LEFT.i = RIGHT.i AND RIGHT.id>'B', trans(LEFT, RIGHT, COUNTER), LOOKUP);
+j49 := JOIN(ds9, ds8, LEFT.i = RIGHT.i, transkip(LEFT, RIGHT, COUNTER), RIGHT ONLY);
 
 sequential(
     output(j1),
@@ -152,6 +156,6 @@ sequential(
     output(j45),
     output(j46),
     output(j47),
-    output(j48)
+    output(j48),
+    output(j49),
 );
-

+ 147 - 143
testing/regress/ecl/key/joinattr2.xml

@@ -1,239 +1,243 @@
 <Dataset name='Result 1'>
- <Row><idl>A</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>C</idl><idr>D</idr></Row>
+ <Row><idl>A</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>D</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 2'>
- <Row><idl>A</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>C</idl><idr>D</idr></Row>
+ <Row><idl>A</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>D</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 3'>
- <Row><idl>D</idl><idr>A</idr></Row>
- <Row><idl>D</idl><idr>B</idr></Row>
- <Row><idl>D</idl><idr>C</idr></Row>
+ <Row><idl>D</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>D</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>D</idl><idr>C</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 4'>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>D</idr></Row>
- <Row><idl>C</idl><idr>E</idr></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>E</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 5'>
- <Row><idl>A</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>C</idl><idr>D</idr></Row>
+ <Row><idl>A</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>D</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 6'>
- <Row><idl>D</idl><idr>A</idr></Row>
- <Row><idl>D</idl><idr>B</idr></Row>
- <Row><idl>D</idl><idr>C</idr></Row>
+ <Row><idl>D</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>D</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>D</idl><idr>C</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 7'>
- <Row><idl>A</idl><idr>F</idr></Row>
- <Row><idl>A</idl><idr>G</idr></Row>
+ <Row><idl>A</idl><idr>F</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>G</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 8'>
- <Row><idl>A</idl><idr>E</idr></Row>
- <Row><idl>A</idl><idr>F</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
- <Row><idl>B</idl><idr>F</idr></Row>
- <Row><idl>C</idl><idr>E</idr></Row>
- <Row><idl>C</idl><idr>F</idr></Row>
+ <Row><idl>A</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>F</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>F</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>F</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 9'>
- <Row><idl>A</idl><idr>C</idr></Row>
- <Row><idl>A</idl><idr>D</idr></Row>
- <Row><idl>A</idl><idr>E</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
+ <Row><idl>A</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>A</idl><idr>E</idr><cnt>3</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 10'>
- <Row><idl>A</idl><idr>C</idr></Row>
- <Row><idl>A</idl><idr>D</idr></Row>
- <Row><idl>A</idl><idr>E</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
+ <Row><idl>A</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>A</idl><idr>E</idr><cnt>3</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 11'>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>D</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>D</idr></Row>
- <Row><idl>C</idl><idr>E</idr></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>D</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>E</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 12'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 13'>
- <Row><idl>A</idl><idr>E</idr></Row>
- <Row><idl>A</idl><idr>F</idr></Row>
- <Row><idl>A</idl><idr>G</idr></Row>
- <Row><idl>B</idl><idr>E</idr></Row>
- <Row><idl>B</idl><idr>F</idr></Row>
- <Row><idl>B</idl><idr>G</idr></Row>
- <Row><idl>C</idl><idr>E</idr></Row>
- <Row><idl>C</idl><idr>F</idr></Row>
- <Row><idl>C</idl><idr>G</idr></Row>
+ <Row><idl>A</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>F</idr><cnt>2</cnt></Row>
+ <Row><idl>A</idl><idr>G</idr><cnt>3</cnt></Row>
+ <Row><idl>B</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>F</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>G</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>E</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>F</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>G</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 14'>
 </Dataset>
 <Dataset name='Result 15'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 16'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 17'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 18'>
 </Dataset>
 <Dataset name='Result 19'>
- <Row><idl>C</idl><idr> </idr></Row>
- <Row><idl>D</idl><idr> </idr></Row>
- <Row><idl>E</idl><idr> </idr></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>D</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>E</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 20'>
- <Row><idl>A</idl><idr> </idr></Row>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 21'>
- <Row><idl>A</idl><idr> </idr></Row>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 22'>
- <Row><idl>A</idl><idr> </idr></Row>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 23'>
- <Row><idl> </idl><idr>A</idr></Row>
+ <Row><idl> </idl><idr>A</idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 24'>
 </Dataset>
 <Dataset name='Result 25'>
- <Row><idl>A</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 26'>
- <Row><idl>A</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 27'>
- <Row><idl>A</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 28'>
- <Row><idl> </idl><idr>A</idr></Row>
- <Row><idl> </idl><idr>B</idr></Row>
- <Row><idl> </idl><idr>C</idr></Row>
+ <Row><idl> </idl><idr>A</idr><cnt>0</cnt></Row>
+ <Row><idl> </idl><idr>B</idr><cnt>0</cnt></Row>
+ <Row><idl> </idl><idr>C</idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 29'>
 </Dataset>
 <Dataset name='Result 30'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 31'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 32'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 33'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 34'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 35'>
 </Dataset>
 <Dataset name='Result 36'>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 37'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 38'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 39'>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
 </Dataset>
 <Dataset name='Result 40'>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 41'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>A</idl><idr>B</idr></Row>
- <Row><idl>A</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>A</idl><idr>C</idr><cnt>3</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 42'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 43'>
 </Dataset>
 <Dataset name='Result 44'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>A</idl><idr>B</idr></Row>
- <Row><idl>A</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>B</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>B</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>A</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>A</idl><idr>C</idr><cnt>3</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>3</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>B</idr><cnt>2</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>3</cnt></Row>
 </Dataset>
 <Dataset name='Result 45'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 46'>
- <Row><idl>A</idl><idr>A</idr></Row>
- <Row><idl>B</idl><idr>A</idr></Row>
- <Row><idl>C</idl><idr>A</idr></Row>
+ <Row><idl>A</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>A</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>A</idr><cnt>1</cnt></Row>
 </Dataset>
 <Dataset name='Result 47'>
- <Row><idl>A</idl><idr> </idr></Row>
- <Row><idl>B</idl><idr> </idr></Row>
- <Row><idl>C</idl><idr> </idr></Row>
+ <Row><idl>A</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>B</idl><idr> </idr><cnt>0</cnt></Row>
+ <Row><idl>C</idl><idr> </idr><cnt>0</cnt></Row>
 </Dataset>
 <Dataset name='Result 48'>
- <Row><idl>A</idl><idr>C</idr></Row>
- <Row><idl>B</idl><idr>C</idr></Row>
- <Row><idl>C</idl><idr>C</idr></Row>
+ <Row><idl>A</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>B</idl><idr>C</idr><cnt>1</cnt></Row>
+ <Row><idl>C</idl><idr>C</idr><cnt>1</cnt></Row>
+</Dataset>
+<Dataset name='Result 49'>
+ <Row><idl> </idl><idr>B</idr><cnt>0</cnt></Row>
+ <Row><idl> </idl><idr>C</idr><cnt>0</cnt></Row>
 </Dataset>

+ 2 - 2
thorlcr/activities/keyedjoin/thkeyedjoinslave.cpp

@@ -2139,7 +2139,7 @@ public:
                             {
                                 case TAKkeyedjoin:
                                 {
-                                    transformedSize = helper->transform(row.ensureRow(), djg->queryLeft(), defaultRight, 0);
+                                    transformedSize = helper->transform(row.ensureRow(), djg->queryLeft(), defaultRight, (__uint64)0, 0U);
                                     break;
                                 }
                                 case TAKkeyeddenormalize:
@@ -2174,7 +2174,7 @@ public:
                                     offset_t fpos;
                                     const void *rhs = djg->queryRow(currentMatchIdx, fpos);
                                     row.ensureRow();
-                                    transformedSize = helper->transform(row, djg->queryLeft(), rhs, fpos);
+                                    transformedSize = helper->transform(row, djg->queryLeft(), rhs, fpos, currentMatchIdx+1);
                                     if (transformedSize)
                                     {
                                         currentAdded++;

+ 6 - 6
thorlcr/activities/lookupjoin/thlookupjoinslave.cpp

@@ -629,10 +629,6 @@ public:
     {
         return helper->match(lhs, rhsrow);
     }
-    inline const size32_t joinTransform(ARowBuilder &rowBuilder, const void *lhs, const void *rhsrow)
-    {
-        return helper->transform(rowBuilder, lhs, rhsrow);
-    }
     inline const size32_t joinTransform(ARowBuilder &rowBuilder, const void *left, const void *right, unsigned numRows, const void **rows)
     {
         return helper->transform(rowBuilder, left, right, numRows, rows);
@@ -776,6 +772,7 @@ protected:
     rowidx_t nextRhsRow;
     unsigned keepLimit;
     unsigned joined;
+    unsigned joinCounter;
     OwnedConstThorRow defaultLeft;
 
     bool leftMatch, grouped;
@@ -979,6 +976,7 @@ protected:
                 if (NULL == rhsNext)
                 {
                     leftRow.setown(left->nextRow());
+                    joinCounter = 0;
                     if (leftRow)
                     {
                         eog = false;
@@ -1018,7 +1016,7 @@ protected:
                             leftMatch = true;
                             if (!exclude)
                             {
-                                size32_t sz = HELPERBASE::joinTransform(rowBuilder, leftRow, rhsNext);
+                                size32_t sz = HELPERBASE::joinTransform(rowBuilder, leftRow, rhsNext, ++joinCounter);
                                 if (sz)
                                 {
                                     OwnedConstThorRow row = rowBuilder.finalizeRowClear(sz);
@@ -1037,7 +1035,7 @@ protected:
                     }
                     if (!leftMatch && NULL == rhsNext && 0!=(flags & JFleftouter))
                     {
-                        size32_t sz = HELPERBASE::joinTransform(rowBuilder, leftRow, defaultRight);
+                        size32_t sz = HELPERBASE::joinTransform(rowBuilder, leftRow, defaultRight, 0);
                         if (sz)
                             ret.setown(rowBuilder.finalizeRowClear(sz));
                     }
@@ -1068,6 +1066,7 @@ public:
         leftITDL = rightITDL = NULL;
 
         joined = 0;
+        joinCounter = 0;
         leftMatch = false;
         returnMany = false;
 
@@ -1116,6 +1115,7 @@ public:
         gotRHS = false;
         nextRhsRow = 0;
         joined = 0;
+        joinCounter = 0;
         leftMatch = false;
         rhsNext = NULL;
         rhsTableLen = 0;

+ 23 - 14
thorlcr/activities/msort/thsortu.cpp

@@ -298,6 +298,7 @@ class CJoinHelper : public CSimpleInterface, implements IJoinHelper
     RtlDynamicRowBuilder denormTmp;
     CThorExpandingRowArray denormRows;
     unsigned denormCount;
+    unsigned joinCounter;
     size32_t outSz;
     unsigned rightidx;
     enum { JScompare, JSmatch, JSrightgrouponly, JSonfail } state;
@@ -363,6 +364,7 @@ public:
         kind = activity.queryContainer().getKind();
         helper = _helper; 
         denormCount = 0;
+        joinCounter = 0;
         outSz = 0;
         lhsProgressCount = rhsProgressCount = 0;
         keepmax = (unsigned)-1;
@@ -492,10 +494,12 @@ public:
                 nextleft.setown(strmL->nextRow());
                 if (!nextleft) 
                     break;
+
                 lhsProgressCount++;
                 if (!firstonlyL || (lhsProgressCount==1) || (compareL->docompare(prevleft,nextleft)!=0)) {
                     denormLhs.set(nextleft.get());
                     nextleftgot = true;
+                    joinCounter = 0;
                     return true;
                 }
             }
@@ -588,7 +592,7 @@ public:
                         case TAKselfjoinlight:
                         case TAKlookupjoin:
                         case TAKsmartjoin:
-                            gotsz = helper->transform(ret, defaultLeft, nextright);
+                            gotsz = helper->transform(ret, defaultLeft, nextright, 0);
                             nextR();
                             break;
                         default:
@@ -645,7 +649,7 @@ public:
                     case TAKlookupjoin:
                     case TAKsmartjoin:
                         if (!rightgroupmatched[rightidx]) 
-                            gotsz = helper->transform(ret, defaultLeft, rightgroup.query(rightidx));
+                            gotsz = helper->transform(ret, defaultLeft, rightgroup.query(rightidx), 0);
                         rightidx++;
                         break;
                     default:
@@ -675,7 +679,7 @@ public:
                     case TAKselfjoinlight:
                     case TAKlookupjoin:
                     case TAKsmartjoin:
-                        gotsz = helper->transform(ret, nextleft, defaultRight);
+                        gotsz = helper->transform(ret, nextleft, defaultRight, 0);
                         break;
                     default:
                         throwUnexpected();
@@ -714,7 +718,7 @@ public:
             if (r==Onext) {
                 // JCSMORE - I can't see when this can happen? if r==Onext, l is always Oouter.
                 if (!exclude) 
-                    gotsz = helper->transform(ret,nextleft,nextright);
+                    gotsz = helper->transform(ret,nextleft,nextright,++joinCounter);
                 rightmatched = true;
             }
             else {
@@ -752,7 +756,7 @@ public:
                         case TAKselfjoinlight:
                         case TAKlookupjoin:
                         case TAKsmartjoin:
-                            gotsz = helper->transform(ret,nextleft,rightgroup.query(rightidx));
+                            gotsz = helper->transform(ret,nextleft,rightgroup.query(rightidx), ++joinCounter);
                             break;
                         default:
                             throwUnexpected();
@@ -988,6 +992,7 @@ class SelfJoinHelper: public CSimpleInterface, implements IJoinHelper
     bool *abort;
     unsigned atmost;
     rowcount_t progressCount;
+    unsigned joinCounter;
     unsigned keepmax;
     unsigned abortlimit;
     unsigned keepremaining;
@@ -1076,6 +1081,7 @@ public:
         keepremaining = keepmax;
         outputmetaL = _outputmeta;
         progressCount = 0;
+        joinCounter = 0;
         return true;
     }
 
@@ -1198,6 +1204,7 @@ retry:
                     }
                     leftidx = 0;
                     rightidx = 0;
+                    joinCounter = 0;
                     leftmatched = false;
                     if (state==JSload) {     // catch atmost above
                         rightmatched = (bool *)rightmatchedbuf.clear().reserve(curgroup.ordinality());
@@ -1217,7 +1224,7 @@ retry:
                                 if (keepremaining>0) {
                                     if (!exclude) {
                                         RtlDynamicRowBuilder rtmp(allocator);
-                                        size32_t sz = helper->transform(rtmp,l,r);
+                                        size32_t sz = helper->transform(rtmp,l,r,++joinCounter);
                                         if (sz)
                                             ret.setown(rtmp.finalizeRowClear(sz));
                                     }
@@ -1233,15 +1240,16 @@ retry:
                             }
                             rightidx++;
                         }
-                        else { // right all done 
+                        else { // right all done
                             if (leftouter&&!leftmatched) {
                                 RtlDynamicRowBuilder rtmp(allocator);
-                                size32_t sz = helper->transform(rtmp, l, defaultRight);
+                                size32_t sz = helper->transform(rtmp, l, defaultRight, 0);
                                 if (sz)
                                     ret.setown(rtmp.finalizeRowClear(sz));
                             }
                             keepremaining = keepmax; // lefts don't count in keep
                             rightidx = 0;
+                            joinCounter = 0;
                             leftidx++;
                             if ((leftidx>=curgroup.ordinality())||(firstonlyL&&(leftidx>0)))
                                 state = JSrightonly;
@@ -1254,14 +1262,14 @@ retry:
                     // must be left outer after atmost to get here
                     if (leftidx<curgroup.ordinality()) {
                         RtlDynamicRowBuilder rtmp(allocator);
-                        size32_t sz = helper->transform(rtmp, curgroup.query(leftidx), defaultRight);
+                        size32_t sz = helper->transform(rtmp, curgroup.query(leftidx), defaultRight, 0);
                         if (sz)
                             ret.setown(rtmp.finalizeRowClear(sz));
                         leftidx++;
                     }
                     else if (getRow() && (compare->docompare(nextrow,curgroup.query(0))==0)) {
                         RtlDynamicRowBuilder rtmp(allocator);
-                        size32_t sz = helper->transform(rtmp, nextrow, defaultRight);
+                        size32_t sz = helper->transform(rtmp, nextrow, defaultRight, 0);
                         if (sz)
                             ret.setown(rtmp.finalizeRowClear(sz));
                         next();
@@ -1274,7 +1282,7 @@ retry:
                     if (rightouter&&(rightidx<curgroup.ordinality())) {
                         if (!rightmatched[rightidx]) {
                             RtlDynamicRowBuilder rtmp(allocator);
-                            size32_t sz = helper->transform(rtmp, defaultLeft,curgroup.query(rightidx));
+                            size32_t sz = helper->transform(rtmp, defaultLeft,curgroup.query(rightidx), 0);
                             if (sz)
                                 ret.setown(rtmp.finalizeRowClear(sz));
                         }
@@ -1528,13 +1536,14 @@ public:
         ForEachItemIn(leftidx,work.lgroup)
         {
             bool lmatched = !leftouter;
+            unsigned joinCounter = 0;
             for (unsigned rightidx=0; rightidx<rgroup.ordinality(); rightidx++) {
                 if (helper->match(work.lgroup.query(leftidx),rgroup.query(rightidx))) {
                     lmatched = true;
                     if (rightouter)
                         rmatched[rightidx] = true;
                     RtlDynamicRowBuilder ret(theAllocator);
-                    size32_t sz = exclude?0:helper->transform(ret,work.lgroup.query(leftidx),rgroup.query(rightidx));
+                    size32_t sz = exclude?0:helper->transform(ret,work.lgroup.query(leftidx),rgroup.query(rightidx),++joinCounter);
                     if (sz)
                         writer.putRow(ret.finalizeRowClear(sz));
 
@@ -1542,7 +1551,7 @@ public:
             }
             if (!lmatched) {
                 RtlDynamicRowBuilder ret(theAllocator);
-                size32_t sz =  helper->transform(ret, work.lgroup.query(leftidx), defaultRight);
+                size32_t sz =  helper->transform(ret, work.lgroup.query(leftidx), defaultRight, 0);
                 if (sz)
                     writer.putRow(ret.finalizeRowClear(sz));
             }
@@ -1551,7 +1560,7 @@ public:
             ForEachItemIn(rightidx2,rgroup) {
                 if (!rmatched[rightidx2]) {
                     RtlDynamicRowBuilder ret(theAllocator);
-                    size32_t sz =  helper->transform(ret, defaultLeft, rgroup.query(rightidx2));
+                    size32_t sz =  helper->transform(ret, defaultLeft, rgroup.query(rightidx2), 0);
                     if (sz)
                         writer.putRow(ret.finalizeRowClear(sz));
                 }