Browse Source

Merge remote-tracking branch 'origin/candidate-4.0.0' into closedown-4.0.x

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 12 years ago
parent
commit
f4d89fa0e8

+ 1 - 1
CMakeLists.txt

@@ -268,7 +268,7 @@ if (NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.1")
 
             set ( CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/initfiles/sbin/prerm" )
             set ( CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/initfiles/bash/sbin/deb/postrm" )
-                else()
+        else()
             message("WARNING: Unsupported package ${packageManagement}.")
         endif ()
 

+ 11 - 0
common/remote/sockfile.cpp

@@ -1547,6 +1547,17 @@ public:
         }
         return clientCrit.getClear();
     }
+    unsigned getHashFromElement(const void *e) const
+    {
+        const CEndpointCS &elem=*(const CEndpointCS *)e;
+        return getHashFromFindParam(elem.queryFindParam());
+    }
+
+    unsigned getHashFromFindParam(const void *fp) const
+    {
+        return ((const SocketEndpoint *)fp)->hash(0);
+    }
+
     void removeExact(CEndpointCS *clientCrit)
     {
         CriticalBlock b(crit);

+ 4 - 0
common/thorhelper/roxierow.cpp

@@ -49,6 +49,8 @@ public:
     {
         byte * ptr = static_cast<byte *>(_ptr);
         memsize_t capacity = RoxieRowCapacity(ptr);
+        if (capacity < size + extraSize)
+            throw MakeStringException(0, "Data was written past the end of the row - allocated %d, written %d", capacity - extraSize, size);
         memset(ptr+size, 0, capacity - size - extraSize);
         unsigned short * check = reinterpret_cast<unsigned short *>(ptr + capacity - extraSize);
         *check = crc16(ptr, capacity-extraSize, 0);
@@ -78,6 +80,8 @@ public:
     {
         byte * ptr = static_cast<byte *>(_ptr);
         memsize_t capacity = RoxieRowCapacity(ptr);
+        if (capacity < size + extraSize)
+            throw MakeStringException(0, "Data was written past the end of the row - allocated %d, written %d", capacity - extraSize, size);
         memset(ptr+size, 0, capacity - size - extraSize);
         unsigned short * check = reinterpret_cast<unsigned short *>(ptr + capacity - extraSize);
         *check = chksum16(ptr, capacity-extraSize);

+ 85 - 78
dali/base/dadfs.cpp

@@ -6069,13 +6069,25 @@ public:
 
 #define GROUP_CACHE_INTERVAL (1000*60)
 
+class CNamedGroupCacheEntry: public CInterface
+{
+public:
+    Linked<IGroup> group;
+    StringAttr name;
+    StringAttr groupdir;
+    unsigned cachedtime;
+
+    CNamedGroupCacheEntry(IGroup *_group, const char *_name, const char *_dir)
+    : group(_group), name(_name), groupdir(_dir)
+    {
+        cachedtime = msTick();
+    }
+};
+
 class CNamedGroupStore: public CInterface, implements INamedGroupStore
 {
     CriticalSection cachesect;
-    Owned<IGroup> cachedgroup;
-    StringAttr cachedname;
-    StringAttr cachedgroupdir;
-    unsigned cachedtime;
+    CIArrayOf<CNamedGroupCacheEntry> cache;
     unsigned defaultTimeout;
 
 public:
@@ -6084,7 +6096,6 @@ public:
     CNamedGroupStore()
     {
         defaultTimeout = INFINITE;
-        cachedtime = 0;
     }
 
     IGroup *dolookup(const char *logicalgroupname,IRemoteConnection *conn, StringBuffer *dirret)
@@ -6096,20 +6107,6 @@ public:
             return NULL;
         gname.toLowerCase();
         logicalgroupname = gname.str();
-        if ((gname.length()>9)&&(memcmp(logicalgroupname,"foreign::",9)==0)) {
-            StringBuffer eps;
-            const char *s = logicalgroupname+9;
-            while (*s&&((*s!=':')||(s[1]!=':')))
-                eps.append(*(s++));
-            if (*s) {
-                s+=2;
-                if (*s) {
-                    Owned<INode> dali = createINode(eps.str());
-                    if (dali) 
-                        return getRemoteGroup(dali,s,FOREIGN_DALI_TIMEOUT,dirret);
-                }
-            }
-        }
         bool isiprange = (*logicalgroupname!=0);
         for (const char *s1=logicalgroupname;*s1;s1++)
             if (isalpha(*s1)) {
@@ -6152,28 +6149,48 @@ public:
             logicalgroupname = gname.str();
         }
         StringAttr groupdir;
+        bool cached = false;
+        unsigned timeNow = msTick();
         {
             CriticalBlock block(cachesect);
-            if (cachedgroup.get()) {
-                if (msTick()-cachedtime>GROUP_CACHE_INTERVAL) {
-                    cachedgroup.clear();
-                    cachedname.clear();
-                    cachedgroupdir.clear();
+            ForEachItemInRev(idx, cache)
+            {
+                CNamedGroupCacheEntry &entry = cache.item(idx);
+                if (timeNow-entry.cachedtime > GROUP_CACHE_INTERVAL)
+                {
+                    cache.remove(idx);
                 }
-                else if (strcmp(gname.str(),cachedname.get())==0) {
-                    cachedtime = msTick();
-                    if (range.length()==0) {
+                else if (strcmp(gname.str(),entry.name.get())==0)
+                {
+                    cached = true;
+                    if (range.length()==0)
+                    {
                         if (dirret)
-                            dirret->append(cachedgroupdir);
-                        return cachedgroup.getLink();
+                            dirret->append(entry.groupdir);
+                        return entry.group.getLink();
                     }
                     // there is a range so copy to epa
-                    cachedgroup->getSocketEndpoints(epa);
-                    groupdir.set(cachedgroupdir);
+                    entry.group->getSocketEndpoints(epa);
+                    groupdir.set(entry.groupdir);
+                    break;
                 }
             }
         }
-        if (epa.ordinality()==0) {
+        if ((gname.length()>9)&&(memcmp(logicalgroupname,"foreign::",9)==0)) {
+            StringBuffer eps;
+            const char *s = logicalgroupname+9;
+            while (*s&&((*s!=':')||(s[1]!=':')))
+                eps.append(*(s++));
+            if (*s) {
+                s+=2;
+                if (*s) {
+                    Owned<INode> dali = createINode(eps.str());
+                    if (!dali || !getRemoteGroup(dali, s, FOREIGN_DALI_TIMEOUT, groupdir, epa))
+                        return NULL;
+                }
+            }
+        }
+        else if (epa.ordinality()==0) {
             struct sLock
             {
                 sLock()  { lock = NULL; };
@@ -6196,30 +6213,33 @@ public:
                 epa.append(ep);
             }
         }
-        IGroup *ret = createIGroup(epa);
+        Owned<IGroup> ret = createIGroup(epa);
+        if (!cached)
         {
             CriticalBlock block(cachesect);
-            cachedgroup.set(ret);
-            cachedname.set(gname);
-            cachedgroupdir.set(groupdir);
-            cachedtime = msTick();
+            cache.append(*new CNamedGroupCacheEntry(ret, gname, groupdir));
         }
-        if (range.length()) {
+        if (range.length())
+        {
             SocketEndpointArray epar;
             const char *s = range.str();
-            while (*s) {
+            while (*s)
+            {
                 unsigned start = 0;
-                while (isdigit(*s)) {
+                while (isdigit(*s))
+                {
                     start = start*10+*s-'0';
                     s++;
                 }
                 if (!start)
                     break;
                 unsigned end;
-                if (*s=='-') {
+                if (*s=='-')
+                {
                     s++;
                     end = 0;
-                    while (isdigit(*s)) {
+                    while (isdigit(*s))
+                    {
                         end = end*10+*s-'0';
                         s++;
                     }
@@ -6228,7 +6248,8 @@ public:
                 }
                 else 
                     end = start;
-                if ((start>epa.ordinality())||(end>epa.ordinality())) {
+                if ((start>epa.ordinality())||(end>epa.ordinality()))
+                {
                     s = range.str();
                     break;
                 }
@@ -6244,12 +6265,11 @@ public:
             }
             if (*s) 
                 throw MakeStringException(-1,"Invalid group range %s",range.str());
-            ::Release(ret);
-            ret = createIGroup(epar);
+            ret.setown(createIGroup(epar));
         }
         if (dirret)
             dirret->append(groupdir);
-        return ret;
+        return ret.getClear();
     }
 
     IGroup *lookup(const char *logicalgroupname)
@@ -6337,11 +6357,10 @@ public:
         connlock.conn->queryRoot()->removeProp(prop.str()); 
         doadd(connlock,name.str(),group,cluster,dir);
         {                                                           
-            CriticalBlock block(cachesect);                     
-            cachedgroup.set(group); // may be NULL
-            cachedname.set(name.str());
-            cachedgroupdir.set(dir);
-            cachedtime = msTick();
+            CriticalBlock block(cachesect);
+            cache.kill();
+            if (group)
+                cache.append(*new CNamedGroupCacheEntry(group, name.str(), dir));
         }
     }
 
@@ -6408,12 +6427,18 @@ public:
             }
         }
         CriticalBlock block(cachesect);
-        cachedgroup.clear();
-        cachedname.clear();
-        cachedgroupdir.clear();
+        cache.kill();
     }
 
-    IGroup *getRemoteGroup(const INode *foreigndali, const char *gname, unsigned foreigndalitimeout, StringBuffer *dirret)
+    unsigned setDefaultTimeout(unsigned timems)
+    {
+        unsigned ret = defaultTimeout;
+        defaultTimeout = timems;
+        return ret;
+    }
+
+private:
+    bool getRemoteGroup(const INode *foreigndali, const char *gname, unsigned foreigndalitimeout, StringAttr &groupdir, SocketEndpointArray &epa)
     {
         StringBuffer lcname(gname);
         gname = lcname.trim().toLowerCase().str();
@@ -6423,7 +6448,7 @@ public:
         foreignDaliSendRecv(foreigndali,mb,foreigndalitimeout);
         checkDfsReplyException(mb);
         if (mb.length()==0)
-            return NULL;
+            return false;
         byte ok;
         mb.read(ok);
         if (ok!=1) {
@@ -6432,39 +6457,21 @@ public:
                 mb.skip(mbsz-1);
                 mb.read(ok);
                 if (ok!=1) 
-                    return NULL;
+                    return false;
             }
             else
-                return NULL;
+                return false;
         }
         Owned<IPropertyTree> pt = createPTree(mb);
         Owned<IPropertyTreeIterator> pe = pt->getElements("Node");
-        SocketEndpointArray epa;
+        groupdir.set(pt->queryProp("@dir"));
         ForEach(*pe) {
             SocketEndpoint ep(pe->query().queryProp("@ip"));
             epa.append(ep);
         }
-        IGroup *ret = createIGroup(epa);
-        {
-            CriticalBlock block(cachesect);
-            cachedgroup.set(ret);
-            cachedname.set(gname);
-            cachedgroupdir.set(pt->queryProp("@dir"));
-            if (dirret)
-                dirret->append(cachedgroupdir);
-            cachedtime = msTick();
-        }
-        return ret;
+        return epa.ordinality() > 0;
     }
 
-    unsigned setDefaultTimeout(unsigned timems)
-    {
-        unsigned ret = defaultTimeout;
-        defaultTimeout = timems;
-        return ret;
-    }
-
-
 };
 
 static CNamedGroupStore *groupStore = NULL;

+ 0 - 1
dali/base/dadfs.hpp

@@ -594,7 +594,6 @@ interface INamedGroupStore: implements IGroupResolver
     virtual bool find(IGroup *grp, StringBuffer &lname, bool add=false) = 0;
     virtual void addUnique(IGroup *group,StringBuffer &lname,const char *dir=NULL) = 0;
     virtual void swapNode(const IpAddress &from, const IpAddress &to) = 0;
-    virtual IGroup *getRemoteGroup(const INode *foreigndali, const char *gname, unsigned foreigndalitimeout=FOREIGN_DALI_TIMEOUT, StringBuffer *dir=NULL) = 0;
     virtual IGroup *lookup(const char *logicalgroupname, StringBuffer &dir) = 0;
     virtual unsigned setDefaultTimeout(unsigned timems) = 0;                                    // sets default timeout for SDS connections and locking                                                                                         // returns previous value
 

+ 12 - 9
dali/dfu/dfuutil.cpp

@@ -402,11 +402,9 @@ class CFileCloner
             throw afor2.exc.getClear();
     }
 
-
-
     void cloneSubFile(IPropertyTree *ftree,const char *destfilename, INode *srcdali)   // name already has prefix added
     {
-        Owned<IFileDescriptor> srcfdesc = deserializeFileDescriptorTree(ftree,&queryNamedGroupStore(),0);
+        Owned<IFileDescriptor> srcfdesc = deserializeFileDescriptorTree(ftree, NULL, 0);
         const char * kind = srcfdesc->queryProperties().queryProp("@kind");
         bool iskey = kind&&(strcmp(kind,"key")==0);
 
@@ -437,11 +435,6 @@ class CFileCloner
         dstfdesc->addCluster(cluster1,grp1,spec);
         if (iskey&&!cluster2.isEmpty())
             dstfdesc->addCluster(cluster2,grp2,spec2);
-#ifdef _TESTING
-//      LOGFDESC("createFileClone",dstfdesc);
-#endif
-
-
 
         for (unsigned pn=0;pn<srcfdesc->numParts();pn++) {
             offset_t sz = srcfdesc->queryPart(pn)->queryProperties().getPropInt64("@size",-1);
@@ -461,6 +454,17 @@ class CFileCloner
         {
             StringBuffer s;
             dstfdesc->queryProperties().setProp("@cloneFrom", srcdali->endpoint().getUrlStr(s).str());
+            unsigned numClusters = srcfdesc->numClusters();
+            for (unsigned clusterNum = 0; clusterNum < numClusters; clusterNum++)
+            {
+                StringBuffer sourceGroup;
+                srcfdesc->getClusterGroupName(clusterNum, sourceGroup, NULL);
+                Owned<IPropertyTree> groupInfo = createPTree("cloneFromGroup");
+                groupInfo->setProp("@groupName", sourceGroup);
+                ClusterPartDiskMapSpec &spec = srcfdesc->queryPartDiskMapping(clusterNum);
+                spec.toProp(groupInfo);
+                dstfdesc->queryProperties().addPropTree("cloneFromGroup", groupInfo.getClear());
+            }
         }
 
         Owned<IDistributedFile> dstfile = fdir->createNew(dstfdesc);
@@ -552,7 +556,6 @@ public:
         }
     }
 
-
     void cloneSuperFile(const char *filename, CDfsLogicalFileName &dlfn)
     {
         level++;

+ 0 - 1
dali/dfu/dfuutil.hpp

@@ -75,7 +75,6 @@ interface IDFUhelper: extends IInterface
         IPropertyTree *relationships,   // if not NULL, tree will have all relationships filled in
         IUserDescriptor *user
     ) = 0;
-
 };
 
 IDFUhelper *createIDFUhelper();

+ 46 - 6
docs/ECLProgrammersGuide/PRG_Mods/PrG_Creating_example_data.xml

@@ -4,14 +4,54 @@
 <sect1 id="Creating_Example_Data">
   <title><emphasis role="bold">Creating Example Data</emphasis></title>
 
+  <sect2 id="Getting_Code_Files">
+    <title>Getting Code Files</title>
+  <para>All the example code for the <emphasis>Programmer's Guide</emphasis>
+  is available for download from the HPCC Systems website from the same page
+  that the PDF is available (<ulink
+  url="http://hpccsystems.com/download/docs/learning-ecl">click here</ulink>).
+  To make this code available for use in the ECL IDE, you simply:</para>
+
+  <orderedlist>
+    <listitem>
+      <para>Download the ECL_Code_Files.ZIP file.</para>
+    </listitem>
+
+    <listitem>
+      <para>In the ECL IDE, highlight your "My Files" folder, right-click and
+      select "Insert Folder" from the popup menu.</para>
+    </listitem>
+
+    <listitem>
+      <para>Name your new folder "ProgrammersGuide" (please note -- spaces are
+      NOT allowed in your ECL repository folder names).</para>
+    </listitem>
+
+    <listitem>
+      <para>In the ECL IDE, highlight your "ProgrammersGuide" folder,
+      right-click and select "Locate File in Explorer" from the popup
+      menu.</para>
+    </listitem>
+
+    <listitem>
+      <para>Extract all the files from the ECL_Code_Files.ZIP file into your
+      new folder.</para>
+    </listitem>
+  </orderedlist>
+  </sect2>
+
+  <sect2 id="Generating_Files">
+    <title>Generating Files</title>
+
   <para>The code that generates the example data used by all the
   <emphasis>Programmer's Guide</emphasis> articles is contained in a file
-  named Gendata.ECL, which was installed by the ECL IDE installation program.
-  You simply need to open that file in the ECL IDE (select <emphasis
-  role="bold">File &gt; Open</emphasis> from the menu, select the Gendata.ECL
-  file, and it will open in a Builder window) then press the <emphasis
-  role="bold">Go</emphasis> button to generate the data files. The process
-  takes a couple of minutes to run. Here is the code, fully explained.</para>
+  named Gendata.ECL. You simply need to open that file in the ECL IDE (select
+  <emphasis role="bold">File &gt; Open</emphasis> from the menu, select the
+  Gendata.ECL file, and it will open in a Builder window) then press the
+  <emphasis role="bold">Submit</emphasis> button to generate the data files.
+  The process takes a couple of minutes to run. Here is the code, fully
+  explained.</para>
+  </sect2>
 
   <sect2 id="Some_Constants">
     <title>Some Constants</title>

+ 2 - 0
ecl/hqlcpp/hqlcerrors.hpp

@@ -208,6 +208,7 @@
 #define HQLERR_EmbeddedTypeNotSupported_X       4187
 #define HQLERR_MaximumSizeLessThanMinimum_XY    4188
 #define HQLERR_UnexpectedOptionValue_XY         4189
+#define HQLERR_VariableRowMustBeLinked          4190
 
 //Warnings....
 #define HQLWRN_PersistDataNotLikely             4500
@@ -490,6 +491,7 @@
 #define HQLERR_EmbeddedTypeNotSupported_X_Text  "Type %s not supported for embedded/external scripts"
 #define HQLERR_MaximumSizeLessThanMinimum_XY_Text "Maximum size (%u) for this record is lower than the minimum (%u)"
 #define HQLERR_UnexpectedOptionValue_XY_Text    "Unexpected value for option %s: %s"
+#define HQLERR_VariableRowMustBeLinked_Text     "External function '%s' cannot return a non-linked variable length row"
 
 //Warnings.
 #define HQLWRN_CannotRecreateDistribution_Text  "Cannot recreate the distribution for a persistent dataset"

+ 6 - 1
ecl/hqlcpp/hqlcpp.cpp

@@ -5772,11 +5772,16 @@ void HqlCppTranslator::doBuildCall(BuildCtx & ctx, const CHqlBoundTarget * tgt,
                 }
                 else
                 {
+                    if (isVariableSizeRecord(expr->queryRecord()))
+                    {
+                        const char * name = expr->queryName()->str();
+                        throwError1(HQLERR_VariableRowMustBeLinked, name ? name : "");
+                    }
                     resultRow.setown(declareTempRow(ctx, ctx, expr));
                     resultRowBuilder.setown(createRowBuilder(ctx, resultRow));
                     IHqlExpression * bound = resultRowBuilder->queryBound();
                     args.append(*getPointer(bound));
-                    localBound.expr.set(bound);
+                    localBound.expr.setown(getPointer(resultRow->queryBound()));
                 }
                 returnByReference = true;
             }

+ 1 - 0
ecl/hthor/hthor.ipp

@@ -1781,6 +1781,7 @@ public:
 
     virtual void ready();
     virtual bool needsAllocator() const { return true; }    
+    virtual bool isGrouped() { return false; }
 
     //interface IHThorInput
     virtual const void *nextInGroup();

+ 14 - 0
ecl/regress/issue9482.ecl

@@ -0,0 +1,14 @@
+FO := SERVICE
+  {varstring512 mya, varstring512 myb} GetInfo(const varstring in_a, const varstring in_b):c,pure,entrypoint='GetInfo';
+END;
+
+{varstring512 mya, varstring512 myb} FOO(const varstring in_a, const varstring in_b):= BEGINC++
+    struct FO_ { char mya[513]; char myb[513]; };
+    FO_ * FOO = (FO_*)__result;
+    strncpy(FOO->mya, in_a, 512);
+    FOO->mya[512] = 0;
+    strncpy(FOO->myb, in_b, 512);
+    FOO->myb[512] = 0;
+ENDC++;
+
+FOO('a','b');

+ 10 - 0
ecl/regress/issue9482b.ecl

@@ -0,0 +1,10 @@
+
+{varstring mya, varstring myb} FOO(const varstring in_a, const varstring in_b):= BEGINC++
+    size_t lena = strlen(in_a)+1;
+    size_t lenb = strlen(in_a)+1;
+    byte * target = (byte*)__result;
+    memcpy(target, in_a, lena);
+    memcpy(target+lena, in_b, lenb);
+ENDC++;
+
+FOO('abcbdeb','bdedeef');

+ 1 - 1
esp/CMakeLists.txt

@@ -23,5 +23,5 @@ HPCC_ADD_SUBDIRECTORY (scm)
 HPCC_ADD_SUBDIRECTORY (services "PLATFORM")
 HPCC_ADD_SUBDIRECTORY (smc "PLATFORM")
 HPCC_ADD_SUBDIRECTORY (test "PLATFORM")
-HPCC_ADD_SUBDIRECTORY (tools "PLATFORM")
+HPCC_ADD_SUBDIRECTORY (tools "CLIENTTOOLS")
 HPCC_ADD_SUBDIRECTORY (xslt "PLATFORM")

+ 24 - 11
initfiles/bash/etc/init.d/install-init.in

@@ -24,23 +24,29 @@ installConfs ()
 {
     fileName=$1
     configPath=$2
+    mkdir -p ${configPath}
+    mkdir -p ${configPath}/rpmnew
 
     printf "Installing %-44s ..." "${fileName}"
+
     if [ ! -e ${configPath}/${fileName} ]; then
-        if [ -e ${configPath}/rpmnew/${fileName} ]; then
-            cp -f ${configPath}/rpmnew/${fileName} ${configPath}/${fileName}
-            log_success_msg
+        # Always install new files without comment
+        cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/${fileName}
+        cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/rpmnew/${fileName}
+        log_success_msg
+    elif [ -e ${configPath}/rpmnew/${fileName} ] && ! `diff -q ${configPath}/rpmnew/${fileName} ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} >/dev/null` ; then
+        # There are changes in the default config since last installed
+        if ! `diff -q ${configPath}/rpmnew/${fileName} ${configPath}/${fileName} >/dev/null` ; then
+            # User has made their own changes too, so don't overwrite
+            log_failure_msg "Not overwriting modified configuration file ${fileName}"
         else
-            if [ -e ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ]; then
-                cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/${fileName}
-                cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/rpmnew/${fileName}
-                log_success_msg
-            else
-                log_failure_msg "Fail: File doesn't exist in rpmnew"
-            fi
+            # User has NOT made their own changes - ok to update
+            cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/${fileName}
+            cp -f ${INSTALL_DIR}/${configPath}/rpmnew/${fileName} ${configPath}/rpmnew/${fileName}
+            log_success_msg "Updated configuration file ${fileName}"
         fi
     else
-        log_success_msg
+        log_success_msg "No changes to configuration file ${fileName}"
     fi
 }
 
@@ -127,6 +133,9 @@ SECTION=${SECTION:-DEFAULT}
 confToUse="${INSTALL_DIR}${CONFIG_DIR}/${ENV_CONF_FILE}"
 
 if [ -d ${CONFIG_DIR} ]; then
+    if [ -f ${CONFIG_DIR}/installed ] ; then
+        exit 0
+    fi
     if [ -e ${CONFIG_DIR}/${ENV_CONF_FILE} ]; then
         confToUse="${CONFIG_DIR}/${ENV_CONF_FILE}"
     fi
@@ -265,3 +274,7 @@ fi
 chown root:$group ${configs}
 chown -R $user:$group ${configs}/*
 chmod 775 ${configs}
+
+if [ -d ${CONFIG_DIR} ]; then
+    date > ${CONFIG_DIR}/installed
+fi

+ 7 - 2
initfiles/bash/sbin/deb/postrm.in

@@ -17,7 +17,12 @@
 
 ###<REPLACE>###
 
+# On some systems, when running an update (as opposed to a remove) the postinstall script does
+# not get run. To work around that issue, we run the post-install actions in the post-remove script
+# (which IS run)
+#
+# If the install dir is still present post-removal, must be an update
+
 if [ -f "${INSTALL_DIR}/etc/init.d/install-init" ]; then
-        ${INSTALL_DIR}/etc/init.d/install-init
+    ${INSTALL_DIR}/etc/init.d/install-init
 fi
-

+ 2 - 2
initfiles/etc/DIR_NAME/CMakeLists.txt

@@ -25,7 +25,7 @@ FOREACH( iFILES
 ENDFOREACH ( iFILES)
 
 Install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/environment.xml DESTINATION .${CONFIG_DIR}/rpmnew COMPONENT Runtime ) 	#  Don't delete the "."
-Install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/environment.conf DESTINATION ${CONFIG_DIR}/rpmnew COMPONENT Runtime )
-Install ( FILES ${CMAKE_CURRENT_SOURCE_DIR}/genenvrules.conf DESTINATION ${CONFIG_DIR}/rpmnew COMPONENT Runtime )
+Install ( FILES ${CMAKE_CURRENT_BINARY_DIR}/environment.conf DESTINATION .${CONFIG_DIR}/rpmnew COMPONENT Runtime )
+Install ( FILES ${CMAKE_CURRENT_SOURCE_DIR}/genenvrules.conf DESTINATION .${CONFIG_DIR}/rpmnew COMPONENT Runtime )
 
 ADD_SUBDIRECTORY(configmgr)

+ 1 - 1
initfiles/etc/DIR_NAME/environment.conf.in

@@ -1,4 +1,4 @@
-## Default environment configuration file
+## Default environment configuration file for OpenHPCC
 
 [DEFAULT]
 configs=${CONFIG_DIR}

+ 1 - 1
initfiles/etc/DIR_NAME/environment.xml.in

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-## Default environment file single-node hpcc installation
+## Default environment file single-node OpenHPCC installation
 -->
 
 <Environment>

+ 10 - 3
initfiles/sbin/prerm.in

@@ -25,20 +25,27 @@ if [ $configmgr_status -gt 0 ]; then
     exit 1
 fi
 
-
 # Stop all services before cleaning up
 ######################################
 /etc/init.d/hpcc-init stop
 if [ -f "${PID_DIR}/mydafilesrv.pid" ]; then
     /etc/init.d/dafilesrv stop
 fi
-# Removing edits from /etc files
+
+# Remove edits from /etc files
+##############################
 ${INSTALL_DIR}/sbin/rm_conf_settings.sh
 
 
-# Removing symlinks.
+# Remove symlinks
+#################
 ${INSTALL_DIR}/etc/init.d/uninstall-init
 
+# Remove installed flag
+#######################
+if [ -f ${CONFIG_DIR}/installed ] ; then
+    rm ${CONFIG_DIR}/installed
+fi
 
 exit 0
  

+ 2 - 2
plugins/Rembed/Rembed.cpp

@@ -46,8 +46,8 @@ extern "C" EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
         return false;
     pb->magicVersion = PLUGIN_VERSION;
     pb->version = version;
-    pb->moduleName = "R";
-    pb->ECL = NULL;
+    pb->moduleName = "+R+"; // Hack - we don't want to export any ECL, but if we don't export something,
+    pb->ECL = "";           // Hack - the dll is unloaded at startup when compiling, and the R runtime closes stdin when unloaded
     pb->flags = PLUGIN_MULTIPLE_VERSIONS;
     pb->description = "R Embed Helper";
     return true;

+ 72 - 17
roxie/ccd/ccddali.cpp

@@ -274,6 +274,53 @@ private:
         return localTree.getClear();
     }
 
+    IFileDescriptor *recreateCloneSource(IFileDescriptor *srcfdesc, const char *destfilename)
+    {
+        Owned<IFileDescriptor> dstfdesc = createFileDescriptor(srcfdesc->getProperties());
+        // calculate dest dir
+
+        CDfsLogicalFileName dstlfn;
+        if (!dstlfn.setValidate(destfilename,true))
+            throw MakeStringException(-1,"Logical name %s invalid",destfilename);
+
+        StringBuffer dstpartmask;
+        getPartMask(dstpartmask,destfilename,srcfdesc->numParts());
+        dstfdesc->setPartMask(dstpartmask.str());
+        unsigned np = srcfdesc->numParts();
+        dstfdesc->setNumParts(srcfdesc->numParts());
+        DFD_OS os = (getPathSepChar(srcfdesc->queryDefaultDir())=='\\')?DFD_OSwindows:DFD_OSunix;
+        StringBuffer dir;
+        StringBuffer dstdir;
+        makePhysicalPartName(dstlfn.get(),0,0,dstdir,false,os,NULL);
+        dstfdesc->setDefaultDir(dstdir.str());
+
+        for (unsigned pn=0;pn<srcfdesc->numParts();pn++) {
+            offset_t sz = srcfdesc->queryPart(pn)->queryProperties().getPropInt64("@size",-1);
+            if (sz!=(offset_t)-1)
+                dstfdesc->queryPart(pn)->queryProperties().setPropInt64("@size",sz);
+            StringBuffer dates;
+            if (srcfdesc->queryPart(pn)->queryProperties().getProp("@modified",dates))
+                dstfdesc->queryPart(pn)->queryProperties().setProp("@modified",dates.str());
+        }
+
+        const char *cloneFrom = srcfdesc->queryProperties().queryProp("@cloneFrom");
+        Owned<IPropertyTreeIterator> groups = srcfdesc->queryProperties().getElements("cloneFromGroup");
+        ForEach(*groups)
+        {
+            IPropertyTree &elem = groups->query();
+            const char *groupName = elem.queryProp("@groupName");
+            StringBuffer foreignGroup("foreign::");
+            foreignGroup.append(cloneFrom).append("::").append(groupName);
+            Owned<IGroup> group = queryNamedGroupStore().lookup(foreignGroup);  // NOTE - this is cached by the named group store
+            ClusterPartDiskMapSpec dmSpec;
+            dmSpec.fromProp(&elem);
+            dstfdesc->addCluster(groupName, group, dmSpec);
+        }
+
+        return dstfdesc.getClear();
+    }
+
+
 public:
 
     IMPLEMENT_IINTERFACE;
@@ -344,30 +391,38 @@ public:
         return ret.getClear();
     }
 
-    IFileDescriptor *checkClonedFromRemote(const char *_lfn, IFileDescriptor *fdesc, bool cacheIt, bool writeAccess)
+    IFileDescriptor *checkClonedFromRemote(const char *_lfn, IFileDescriptor *fdesc, bool cacheIt)
     {
+        // NOTE - we rely on the fact that  queryNamedGroupStore().lookup caches results,to avoid excessive load on remote dali
         if (_lfn && !strnicmp(_lfn, "foreign", 7)) //if need to support dali hopping should add each remote location
             return NULL;
         if (!fdesc || !fdesc->queryProperties().hasProp("@cloneFrom"))
             return NULL;
-        SocketEndpoint cloneFrom;
-        cloneFrom.set(fdesc->queryProperties().queryProp("@cloneFrom"));
-        if (cloneFrom.isNull())
-            return NULL;
-        CDfsLogicalFileName lfn;
-        lfn.set(_lfn);
-        lfn.setForeign(cloneFrom, false);
-        if (!connected())
-            return resolveCachedLFN(lfn.get());
-        Owned<IDistributedFile> cloneFile = resolveLFN(lfn.get(), cacheIt, writeAccess);
-        if (cloneFile)
+        if (fdesc->queryProperties().hasProp("cloneFromGroup"))
+        {
+            return recreateCloneSource(fdesc, _lfn);
+        }
+        else // Legacy mode - recently cloned files should have the extra info
         {
-            Owned<IFileDescriptor> cloneFDesc = cloneFile->getFileDescriptor();
-            if (cloneFDesc->numParts()==fdesc->numParts())
-                return cloneFDesc.getClear();
+            SocketEndpoint cloneFrom;
+            cloneFrom.set(fdesc->queryProperties().queryProp("@cloneFrom"));
+            if (cloneFrom.isNull())
+                return NULL;
+            CDfsLogicalFileName lfn;
+            lfn.set(_lfn);
+            lfn.setForeign(cloneFrom, false);
+            if (!connected())
+                return resolveCachedLFN(lfn.get());
+            Owned<IDistributedFile> cloneFile = resolveLFN(lfn.get(), cacheIt, false);
+            if (cloneFile)
+            {
+                Owned<IFileDescriptor> cloneFDesc = cloneFile->getFileDescriptor();
+                if (cloneFDesc->numParts()==fdesc->numParts())
+                    return cloneFDesc.getClear();
 
-            StringBuffer s;
-            DBGLOG(ROXIE_MISMATCH, "File %s cloneFrom(%s) mismatch", _lfn, cloneFrom.getIpText(s).str());
+                StringBuffer s;
+                DBGLOG(ROXIE_MISMATCH, "File %s cloneFrom(%s) mismatch", _lfn, cloneFrom.getIpText(s).str());
+            }
         }
         return NULL;
     }

+ 1 - 1
roxie/ccd/ccddali.hpp

@@ -39,7 +39,7 @@ interface IRoxieDaliHelper : extends IInterface
 {
     virtual void commitCache() = 0;
     virtual bool connected() const = 0;
-    virtual IFileDescriptor *checkClonedFromRemote(const char *id, IFileDescriptor *fdesc, bool cacheIt, bool writeAccess) = 0;
+    virtual IFileDescriptor *checkClonedFromRemote(const char *id, IFileDescriptor *fdesc, bool cacheIt) = 0;
     virtual IDistributedFile *resolveLFN(const char *filename, bool cacheIt, bool writeAccess) = 0;
     virtual IFileDescriptor *resolveCachedLFN(const char *filename) = 0;
     virtual IConstWorkUnit *attachWorkunit(const char *wuid, ILoadedDllEntry *source) = 0;

File diff suppressed because it is too large
+ 166 - 619
roxie/ccd/ccdfile.cpp


+ 3 - 12
roxie/ccd/ccdfile.hpp

@@ -23,7 +23,7 @@
 #include "dautils.hpp"
 
 enum RoxieFileStatus { FileSizeMismatch, FileDateMismatch, FileCRCMismatch, FileIsValid, FileNotFound };
-enum RoxieFileType { ROXIE_WU_DLL, ROXIE_PLUGIN_DLL, ROXIE_KEY, ROXIE_FILE, ROXIE_PATCH, ROXIE_BASEINDEX };
+enum RoxieFileType { ROXIE_KEY, ROXIE_FILE };
 interface IFileIOArray;
 interface IRoxieFileCache;
 
@@ -43,15 +43,9 @@ interface ILazyFileIO : extends IFileIO
     virtual int getLinkCount() const = 0;
     virtual bool createHardFileLink() = 0;
 
-    virtual void setBaseIndexFileName(const char *val) =0;
-    virtual const char *queryBaseIndexFileName() = 0;
-    virtual void setPatchFile(ILazyFileIO *val) = 0;
-    virtual ILazyFileIO *queryPatchFile() = 0;
-
     virtual unsigned getLastAccessed() const = 0;
     virtual bool isOpen() const = 0;
     virtual void close() = 0;
-    virtual RoxieFileType getFileType() = 0;
     virtual void setCopying(bool copying) = 0;
     virtual bool isCopying() const = 0;
     virtual IMemoryMappedFile *queryMappedFile() = 0;
@@ -64,9 +58,8 @@ extern ILazyFileIO *createDynamicFile(const char *id, IPartDescriptor *pdesc, Ro
 
 interface IRoxieFileCache : extends IInterface
 {
-    virtual ILazyFileIO *lookupFile(const char *id, unsigned partNo, RoxieFileType fileType, const char *localLocation, const char *baseIndexFileName, ILazyFileIO *patchFile, const StringArray &peerRoxieCopiedLocationInfo, const StringArray &deployedLocationInfo, offset_t size, const CDateTime &modified, bool memFile, bool isRemote, bool startFileCopy, bool doForegroundCopy, unsigned crc, bool isCompressed, const char *lookupDali) = 0;
-    virtual IFileIO *lookupDllFile(const char* dllname, const char *localLocation, const StringArray &remoteNames, unsigned crc, bool isRemote) = 0;
-    virtual RoxieFileStatus fileUpToDate(IFile *f, RoxieFileType fileType, offset_t size, const CDateTime &modified, unsigned crc, const char* id, bool isCompressed) = 0;
+    virtual ILazyFileIO *lookupFile(const char *lfn, RoxieFileType fileType, IPartDescriptor *pdesc, unsigned numParts, const StringArray &deployedLocationInfo, bool startFileCopy) = 0;
+    virtual RoxieFileStatus fileUpToDate(IFile *f, offset_t size, const CDateTime &modified, unsigned crc, bool isCompressed) = 0;
     virtual int numFilesToCopy() = 0;
     virtual void closeExpired(bool remote) = 0;
     virtual StringAttrMapping *queryFileErrorList() = 0;  // returns list of files that could not be open
@@ -146,7 +139,5 @@ extern IRoxieWriteHandler *createRoxieWriteHandler(IRoxieDaliHelper *_daliHelper
 
 extern IRoxieFileCache &queryFileCache();
 extern IMemoryFile *createMemoryFile(const char *fileName);
-extern IDiffFileInfoCache *queryDiffFileInfoCache();
-extern void releaseDiffFileInfoCache();
 
 #endif

+ 0 - 1
roxie/ccd/ccdmain.cpp

@@ -1080,7 +1080,6 @@ int STARTQUERY_API start_query(int argc, const char *argv[])
     cleanupPlugins();
     closeMulticastSockets();
     releaseSlaveDynamicFileCache();
-    releaseDiffFileInfoCache();
     releaseRoxieStateCache();
     setDaliServixSocketCaching(false);  // make sure it cleans up or you get bogus memleak reports
     setNodeCaching(false); // ditto

+ 18 - 10
roxie/ccd/ccdstate.cpp

@@ -264,7 +264,7 @@ protected:
                 if (fd)
                 {
                     Owned <IResolvedFileCreator> result = createResolvedFile(fileName, NULL);
-                    Owned<IFileDescriptor> remoteFDesc = daliHelper->checkClonedFromRemote(fileName, fd, cacheIt, writeAccess);
+                    Owned<IFileDescriptor> remoteFDesc = daliHelper->checkClonedFromRemote(fileName, fd, cacheIt);
                     result->addSubFile(fd.getClear(), remoteFDesc.getClear());
                     return result.getClear();
                 }
@@ -980,6 +980,11 @@ public:
         return serverManager.getLink();
     }
 
+    void getInfo(StringBuffer &reply, const IRoxieContextLogger &logctx) const
+    {
+        reply.appendf(" <PackageSet id=\"%s\" querySet=\"%s\"/>\n", queryPackageId(), querySet.get());
+    }
+
     void resetStats(const char *queryId, const IRoxieContextLogger &logctx)
     {
         CriticalBlock b(updateCrit);
@@ -1290,6 +1295,16 @@ public:
         }
     }
 
+    void getInfo(StringBuffer &reply, const IRoxieContextLogger &logctx) const
+    {
+        reply.append("<PackageSets>\n");
+        ForEachItemIn(idx, allQueryPackages)
+        {
+            allQueryPackages.item(idx).getInfo(reply, logctx);
+        }
+        reply.append("</PackageSets>\n");
+    }
+
     void getStats(StringBuffer &reply, const char *id, const char *action, const char *graphName, const IRoxieContextLogger &logctx) const
     {
         ForEachItemIn(idx, allQueryPackages)
@@ -1660,10 +1675,6 @@ private:
                 defaultWarnTimeLimit[2] = control->getPropInt("@limit", 0);
                 topology->setPropInt("@defaultSLAPriorityTimeWarning", defaultWarnTimeLimit[2]);
             }
-            else if (stricmp(queryName, "control:deleteKeyDiffFiles")==0)
-            {
-                UNIMPLEMENTED;
-            }
             else if (stricmp(queryName, "control:deleteUnneededPhysicalFiles")==0)
             {
                 UNIMPLEMENTED;
@@ -1964,13 +1975,10 @@ private:
                     toXML(stats, reply);
                 }
             }
-            else if (stricmp(queryName, "control:queryDiffFileInfoCache")==0)
-            {
-                queryDiffFileInfoCache()->queryDiffFileNames(reply);
-            }
             else if (stricmp(queryName, "control:queryPackageInfo")==0)
             {
-                UNIMPLEMENTED;
+                ReadLockBlock readBlock(packageCrit);
+                allQueryPackages->getInfo(reply, logctx);
             }
             else if (stricmp(queryName, "control:queryStats")==0)
             {

+ 9 - 0
testing/ecl/countgrouprollup.ecl

@@ -0,0 +1,9 @@
+
+
+ds := DATASET([], { unsigned id; });
+
+g := GROUP(NOFOLD(ds), id);
+
+r := ROLLUP(g, GROUP, TRANSFORM({unsigned i}, SELF.i := COUNT(ROWS(LEFT))));
+
+output(count(r));

+ 3 - 0
testing/ecl/key/countgrouprollup.xml

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

+ 156 - 24
testing/ecl/key/keyed_join.xml

@@ -691,6 +691,72 @@
  <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
 <Dataset name='Result 25'>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>DAVID     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>CLAIRE    SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Full keyed: simple limit(3,skip), LEFT OUTER </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Full keyed: simple limit(3,skip), </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+</Dataset>
+<Dataset name='Result 26'>
  <Row><name>Full keyed: postfilter after fetch           </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: postfilter after fetch           </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: postfilter after fetch           </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -756,7 +822,7 @@
  <Row><name>Full keyed: postfilter after fetch           </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: postfilter after fetch           </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 26'>
+<Dataset name='Result 27'>
  <Row><name>Half keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>CLAIRE    BAYLISS   1                        </rightrec></Row>
  <Row><name>Half keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>CLAIRE    BAYLISS   2                        </rightrec></Row>
  <Row><name>Half keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>CLAIRE    BAYLISS   3                        </rightrec></Row>
@@ -790,7 +856,7 @@
  <Row><name>Half keyed: grouped inner                    </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>KIMBERLY  SMITH     3                        </rightrec></Row>
  <Row><name>Half keyed: grouped inner                    </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>KIMBERLY  SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 27'>
+<Dataset name='Result 28'>
  <Row><name>Half keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -824,7 +890,7 @@
  <Row><name>Half keyed: grouped left only                </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped left only                </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 28'>
+<Dataset name='Result 29'>
  <Row><name>Half keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -890,7 +956,7 @@
  <Row><name>Half keyed: grouped left outer               </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>KIMBERLY  SMITH     3                        </rightrec></Row>
  <Row><name>Half keyed: grouped left outer               </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>KIMBERLY  SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 29'>
+<Dataset name='Result 30'>
  <Row><name>Half keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>CLAIRE    BAYLISS   1                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>CLAIRE    BAYLISS   2                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>CLAIRE    BAYLISS   3                        </rightrec></Row>
@@ -908,7 +974,7 @@
  <Row><name>Half keyed: grouped skip                     </name><leftrec>CLAIRE    SMITH     3                        </leftrec><rightrec>CLAIRE    SMITH     3                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip                     </name><leftrec>CLAIRE    SMITH     4                        </leftrec><rightrec>CLAIRE    SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 30'>
+<Dataset name='Result 31'>
  <Row><name>Half keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -942,7 +1008,7 @@
  <Row><name>Half keyed: grouped skip, left only          </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left only          </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 31'>
+<Dataset name='Result 32'>
  <Row><name>Half keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -992,17 +1058,17 @@
  <Row><name>Half keyed: grouped skip, left outer         </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped skip, left outer         </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 32'>
- <Row><Result_32>64</Result_32></Row>
-</Dataset>
 <Dataset name='Result 33'>
+ <Row><Result_33>64</Result_33></Row>
 </Dataset>
 <Dataset name='Result 34'>
- <Row><Result_34>1</Result_34></Row>
 </Dataset>
 <Dataset name='Result 35'>
+ <Row><Result_35>1</Result_35></Row>
 </Dataset>
 <Dataset name='Result 36'>
+</Dataset>
+<Dataset name='Result 37'>
  <Row><name>Half keyed: grouped atmost(3), LEFT OUTER)   </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped atmost(3), LEFT OUTER)   </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped atmost(3), LEFT OUTER)   </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1068,7 +1134,7 @@
  <Row><name>Half keyed: grouped atmost(3), LEFT OUTER)   </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped atmost(3), LEFT OUTER)   </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 37'>
+<Dataset name='Result 38'>
  <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1102,7 +1168,73 @@
  <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 38'>
+<Dataset name='Result 39'>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>DAVID     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>CLAIRE    SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>Half keyed: grouped limit(3,skip), LEFT OUTER</name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BAYLISS   4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  DOLSON    1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  DOLSON    2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  DOLSON    3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  DOLSON    4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BILLINGTON1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BILLINGTON2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BILLINGTON3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  BILLINGTON4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  SMITH     1                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  SMITH     2                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
+ <Row><name>**onfail** Half keyed: grouped limit(3,skip),</name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
+</Dataset>
+<Dataset name='Result 40'>
  <Row><name>Full keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>CLAIRE    BAYLISS   1                        </rightrec></Row>
  <Row><name>Full keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>CLAIRE    BAYLISS   2                        </rightrec></Row>
  <Row><name>Full keyed: grouped inner                    </name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>CLAIRE    BAYLISS   3                        </rightrec></Row>
@@ -1136,7 +1268,7 @@
  <Row><name>Full keyed: grouped inner                    </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>KIMBERLY  SMITH     3                        </rightrec></Row>
  <Row><name>Full keyed: grouped inner                    </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>KIMBERLY  SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 39'>
+<Dataset name='Result 41'>
  <Row><name>Full keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped left only                </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1170,7 +1302,7 @@
  <Row><name>Full keyed: grouped left only                </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped left only                </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 40'>
+<Dataset name='Result 42'>
  <Row><name>Full keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped left outer               </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1236,7 +1368,7 @@
  <Row><name>Full keyed: grouped left outer               </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>KIMBERLY  SMITH     3                        </rightrec></Row>
  <Row><name>Full keyed: grouped left outer               </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>KIMBERLY  SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 41'>
+<Dataset name='Result 43'>
  <Row><name>Full keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   1                        </leftrec><rightrec>CLAIRE    BAYLISS   1                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   2                        </leftrec><rightrec>CLAIRE    BAYLISS   2                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip                     </name><leftrec>CLAIRE    BAYLISS   3                        </leftrec><rightrec>CLAIRE    BAYLISS   3                        </rightrec></Row>
@@ -1254,7 +1386,7 @@
  <Row><name>Full keyed: grouped skip                     </name><leftrec>CLAIRE    SMITH     3                        </leftrec><rightrec>CLAIRE    SMITH     3                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip                     </name><leftrec>CLAIRE    SMITH     4                        </leftrec><rightrec>CLAIRE    SMITH     4                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 42'>
+<Dataset name='Result 44'>
  <Row><name>Full keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left only          </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1288,7 +1420,7 @@
  <Row><name>Full keyed: grouped skip, left only          </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left only          </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 43'>
+<Dataset name='Result 45'>
  <Row><name>Full keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left outer         </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1338,17 +1470,17 @@
  <Row><name>Full keyed: grouped skip, left outer         </name><leftrec>KELLY     SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped skip, left outer         </name><leftrec>KELLY     SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 44'>
- <Row><Result_44>64</Result_44></Row>
-</Dataset>
-<Dataset name='Result 45'>
-</Dataset>
 <Dataset name='Result 46'>
- <Row><Result_46>1</Result_46></Row>
+ <Row><Result_46>64</Result_46></Row>
 </Dataset>
 <Dataset name='Result 47'>
 </Dataset>
 <Dataset name='Result 48'>
+ <Row><Result_48>1</Result_48></Row>
+</Dataset>
+<Dataset name='Result 49'>
+</Dataset>
+<Dataset name='Result 50'>
  <Row><name>Full keyed: grouped atmost(3), LEFT OUTER    </name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped atmost(3), LEFT OUTER    </name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped atmost(3), LEFT OUTER    </name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>
@@ -1414,7 +1546,7 @@
  <Row><name>Full keyed: grouped atmost(3), LEFT OUTER    </name><leftrec>KIMBERLY  SMITH     3                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped atmost(3), LEFT OUTER    </name><leftrec>KIMBERLY  SMITH     4                        </leftrec><rightrec>                    0                        </rightrec></Row>
 </Dataset>
-<Dataset name='Result 49'>
+<Dataset name='Result 51'>
  <Row><name>Full keyed: grouped limit(3,SKIP), LEFT OUTER</name><leftrec>DAVID     BAYLISS   1                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped limit(3,SKIP), LEFT OUTER</name><leftrec>DAVID     BAYLISS   2                        </leftrec><rightrec>                    0                        </rightrec></Row>
  <Row><name>Full keyed: grouped limit(3,SKIP), LEFT OUTER</name><leftrec>DAVID     BAYLISS   3                        </leftrec><rightrec>                    0                        </rightrec></Row>

+ 9 - 0
testing/ecl/keyed_join.ecl

@@ -157,6 +157,9 @@ Out32 :=JOIN(DG_FlatFile, DG_FlatFileEvens, left.DG_firstname = right.DG_firstna
          AND left.DG_lastname=right.DG_lastname 
       , makePairFK(left, right, 'Full keyed: simple limit(3,skip), LEFT OUTER'), KEYED(DG_indexFileEvens), LIMIT(3,SKIP), LEFT OUTER);
 Out33 :=JOIN(DG_FlatFile, DG_FlatFileEvens, left.DG_firstname = right.DG_firstname 
+         AND left.DG_lastname=right.DG_lastname 
+      , makePairFK(left, right, 'Full keyed: simple limit(3,skip), LEFT OUTER'), KEYED(DG_indexFileEvens), LIMIT(3), ONFAIL(makePairFK(left, right, '**onfail** Full keyed: simple limit(3,skip), LEFT OUTER')), LEFT OUTER);
+Out34 :=JOIN(DG_FlatFile, DG_FlatFileEvens, left.DG_firstname = right.DG_firstname 
          AND left.DG_lastname=right.DG_lastname AND right.DG_parentID=99
       , makePairFK(left, right, 'Full keyed: postfilter after fetch'), KEYED(DG_indexFileEvens), LEFT OUTER);
 
@@ -204,6 +207,10 @@ Out51 :=JOIN(group(DG_FlatFile, DG_firstname), DG_indexFileEvens, left.DG_firstn
 Out52 :=JOIN(group(DG_FlatFile, DG_firstname), DG_indexFileEvens, left.DG_firstname = right.DG_firstname 
          AND left.DG_lastname=right.DG_lastname 
       , makePair(left, right, 'Half keyed: grouped limit(3,skip), LEFT OUTER)'), LIMIT(3,skip), LEFT OUTER);
+Out53 :=JOIN(group(DG_FlatFile, DG_firstname), DG_indexFileEvens, left.DG_firstname = right.DG_firstname 
+         AND left.DG_lastname=right.DG_lastname 
+      , makePair(left, right, 'Half keyed: grouped limit(3,skip), LEFT OUTER)'), LIMIT(3), ONFAIL(makePair(left, right, '**onfail** Half keyed: grouped limit(3,skip), LEFT OUTE')), LEFT OUTER);
+
 
 //fullkeyedjoins(fullkeyedgrouped, group(DG_FlatFile, DG_firstname), 'grouped');
 Out61 :=JOIN(group(DG_FlatFile, DG_firstname), DG_FlatFileEvens, left.DG_firstname = right.DG_firstname 
@@ -281,6 +288,7 @@ output(Out30);
 output(Out31);
 output(Out32);
 output(Out33);
+output(Out34);
 
 output(GROUP(Out41));
 output(GROUP(Out42));
@@ -294,6 +302,7 @@ output(COUNT(Out49));
 output(GROUP(Out50));
 output(GROUP(Out51));
 output(GROUP(Out52));
+output(GROUP(Out53));
 
 output(GROUP(Out61));
 output(GROUP(Out62));

+ 36 - 24
testing/regress/hpcc/regression/regress.py

@@ -83,39 +83,51 @@ class Regression:
         return Suite('setup', self.setupDir, self.dir_a, self.dir_ex,
                      self.dir_r)
 
-    def runSuite(self, name, suite):
-        server = self.config.ip
+    def buildLogging(self, name):
         report = Report(name)
         curTime = time.strftime("%y-%m-%d-%H-%M")
         logName = name + "." + curTime + ".log"
+        log = os.path.join(self.logDir, logName)
+        self.log.addHandler(log, 'DEBUG')
+        return (report, log)
+
+    @staticmethod
+    def displayReport(report):
+        report[0].display(report[1])
+
+    def runSuite(self, name, suite):
+        report = self.buildLogging(name)
         if name == "setup":
             cluster = 'hthor'
         else:
             cluster = name
-        log = os.path.join(self.logDir, logName)
-        self.log.addHandler(log, 'DEBUG')
+
         logging.warn("Suite: %s" % name)
         logging.warn("Queries: %s" % repr(len(suite.getSuite())))
         cnt = 1
         for query in suite.getSuite():
-            logging.warn("%s. Test: %s" % (repr(cnt), query.ecl))
-            ECLCC().makeArchive(query)
-            res = ECLcmd().runCmd("run", cluster, query, report,
-                                  server=server, username=self.config.username,
-                                  password=self.config.password)
-            wuid = query.getWuid()
-            if wuid:
-                url = "http://" + self.config.server
-                url += "/WsWorkunits/WUInfo?Wuid="
-                url += wuid
-            if res:
-                logging.info("Pass %s" % wuid)
-                logging.info("URL %s" % url)
-            else:
-                if not wuid:
-                    logging.error("Fail No WUID")
-                else:
-                    logging.error("Fail %s" % wuid)
-                    logging.error("URL %s" % url)
+            self.runQuery(cluster, query, report, cnt)
             cnt += 1
-        report.display(log)
+        Regression.displayReport(report)
+
+    def runQuery(self, cluster, query, report, cnt=1):
+        logging.warn("%s. Test: %s" % (repr(cnt), query.ecl))
+        ECLCC().makeArchive(query)
+        res = ECLcmd().runCmd("run", cluster, query, report[0],
+                              server=self.config.ip,
+                              username=self.config.username,
+                              password=self.config.password)
+        wuid = query.getWuid()
+        if wuid:
+            url = "http://" + self.config.server
+            url += "/WsWorkunits/WUInfo?Wuid="
+            url += wuid
+        if res:
+            logging.info("Pass %s" % wuid)
+            logging.info("URL %s" % url)
+        else:
+            if not wuid:
+                logging.error("Fail No WUID")
+            else:
+                logging.error("Fail %s" % wuid)
+                logging.error("URL %s" % url)

+ 16 - 2
testing/regress/regress

@@ -21,9 +21,10 @@
 
 import argparse
 import logging
+import os
 
 from hpcc.regression.regress import Regression
-
+from hpcc.util.ecl.file import ECLFile
 
 if __name__ == "__main__":
     prog = "regress"
@@ -45,6 +46,11 @@ if __name__ == "__main__":
     parser_run = subparsers.add_parser('run', help='run help')
     parser_run.add_argument('cluster', help="Run the cluster suite.",
                             nargs='?', default='setup')
+    parser_query = subparsers.add_parser('query', help='query help')
+    parser_query.add_argument('query', help="Run a single query.",
+                              nargs='?')
+    parser_query.add_argument('cluster', help="Cluster for single query run.",
+                            nargs='?', default='setup')
     args = parser.parse_args()
 
     suiteDir = ""
@@ -59,7 +65,15 @@ if __name__ == "__main__":
             print "Avaliable Clusters: "
             for i in Clusters:
                 print i
-        if 'cluster' in args:
+        if 'query' in args:
+            ecl = os.path.join(regress.dir_ec, args.query)
+            eclfile = ECLFile(ecl, regress.dir_a, regress.dir_ex,
+                              regress.dir_r)
+            if not eclfile.testSkip(args.cluster)['skip']:
+                report = regress.buildLogging(args.query)
+                regress.runQuery(args.cluster, eclfile, report)
+                Regression.displayReport(report)
+        elif 'cluster' in args:
             regress.bootstrap()
             if 'setup' in args.cluster:
                 regress.runSuite('setup', regress.setup)

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

@@ -1741,38 +1741,45 @@ public:
             input = NULL;
         }
     }
-    bool abortLimitAction(CJoinGroup *jg, OwnedConstThorRow &row)
+    void doAbortLimit(CJoinGroup *jg)
+    {
+        helper->onMatchAbortLimitExceeded();
+        CommonXmlWriter xmlwrite(0);
+        if (inputHelper && inputHelper->queryOutputMeta() && inputHelper->queryOutputMeta()->hasXML())
+        {
+            inputHelper->queryOutputMeta()->toXML((byte *) jg->queryLeft(), xmlwrite);
+        }
+        throw MakeActivityException(this, 0, "More than %d match candidates in keyed join for row %s", abortLimit, xmlwrite.str());
+    }
+    bool checkAbortLimit(CJoinGroup *jg)
     {
         if (jg->candidateCount() > abortLimit)
         {
             if (0 == (joinFlags & JFmatchAbortLimitSkips))
-            {
-                Owned<IException> e;
-                try
-                {
-                    helper->onMatchAbortLimitExceeded();
-                    CommonXmlWriter xmlwrite(0);
-                    if (inputHelper && inputHelper->queryOutputMeta() && inputHelper->queryOutputMeta()->hasXML())
-                    {
-                        inputHelper->queryOutputMeta()->toXML((byte *) jg->queryLeft(), xmlwrite);
-                    }
-                    throw MakeActivityException(this, 0, "More than %d match candidates in keyed join for row %s", abortLimit, xmlwrite.str());
-                }
-                catch (IException *_e)
-                {
-                    if (!onFailTransform)
-                        throw;
-                    e.setown(_e);
-                }
-                RtlDynamicRowBuilder trow(queryRowAllocator());
-                size32_t transformedSize = helper->onFailTransform(trow, jg->queryLeft(), defaultRight, 0, e.get());
-                if (0 != transformedSize)
-                    row.setown(trow.finalizeRowClear(transformedSize));
-            }
+                doAbortLimit(jg);
             return true;
         }
         return false;
     }
+    bool abortLimitAction(CJoinGroup *jg, OwnedConstThorRow &row)
+    {
+        Owned<IException> e;
+        try
+        {
+            return checkAbortLimit(jg);
+        }
+        catch (IException *_e)
+        {
+            if (!onFailTransform)
+                throw;
+            e.setown(_e);
+        }
+        RtlDynamicRowBuilder trow(queryRowAllocator());
+        size32_t transformedSize = helper->onFailTransform(trow, jg->queryLeft(), defaultRight, 0, e.get());
+        if (0 != transformedSize)
+            row.setown(trow.finalizeRowClear(transformedSize));
+        return true;
+    }
     virtual void onLimitExceeded() { return; }
     
     // IThorSlaveActivity overloaded methods
@@ -2210,12 +2217,15 @@ public:
                             OwnedConstThorRow abortRow;
                             if (abortLimitAction(doneJG, abortRow)) // discard lhs row (yes, even if it is an outer join)
                             {
-                                doneJG.clear();
+                                // don't clear doneJG, in preserveGroups case, it will advance to next, next time around.
+                                if (!preserveGroups)
+                                    doneJG.clear();
                                 if (abortRow.get())
                                 {
                                     dataLinkIncrement();
                                     return abortRow.getClear();
                                 }
+                                continue; // throw away this match
                             }
                             djg.set(next);
                         }
@@ -2244,25 +2254,22 @@ public:
                         doneGroupsDeQueued++;
 #endif
                         OwnedConstThorRow abortRow;
-                        if (abortLimitAction(doneJG, abortRow))
+                        if (!abortLimitAction(doneJG, abortRow))
                         {
-                            doneJG.clear();
-                            if (abortRow.get())
-                            {
-                                dataLinkIncrement();
-                                return abortRow.getClear();
-                            }
-                            continue;
-                        }
-                        else
                             djg.set(doneJG);
+                            currentMatched = djg->rowsSeen();
+                        }
+                        currentAdded = 0;
+                        currentMatchIdx = 0;
                         if (preserveGroups)
                             currentJoinGroupSize = 0;
                         else
                             doneJG.clear();
-                        currentAdded = 0;
-                        currentMatchIdx = 0;
-                        currentMatched = djg->rowsSeen();
+                        if (abortRow.get())
+                        {
+                            dataLinkIncrement();
+                            return abortRow.getClear();
+                        }
                     }
                     else
                     {
@@ -2292,16 +2299,9 @@ public:
                         {
                             unsigned candidateCount = (unsigned) (fpos & KEYEDJOIN_CANDIDATECOUNTMASK);
                             jg->noteCandidates(candidateCount);
-                            OwnedConstThorRow abortRow;
-                            if (abortLimitAction(jg, abortRow)) // might as well abort now if appropriate.
-                            {
-                                if (abortRow.get())
-                                {
-                                    dataLinkIncrement();
-                                    return abortRow.getClear();
-                                }
-                            }
-                            jg->noteEndCandidate();
+                            jg->noteEndCandidate(); // any onFail transform will be done when dequeued
+                            if (!onFailTransform) // unless going to transform later, check and abort now if necessary.
+                                checkAbortLimit(jg);
                         }
                         else
                         {

+ 17 - 24
thorlcr/activities/loop/thloop.cpp

@@ -147,7 +147,6 @@ public:
 class CLoopActivityMaster : public CLoopActivityMasterBase
 {
     IHThorLoopArg *helper;
-    IThorBoundLoopGraph *boundGraph;
     unsigned flags;
     Owned<IBarrier> barrier;
 
@@ -191,7 +190,6 @@ public:
     void init()
     {
         helper = (IHThorLoopArg *) queryHelper();
-        boundGraph = queryContainer().queryLoopGraph();
         flags = helper->getFlags();
         if (TAKloopdataset == container.getKind())
             assertex(flags & IHThorLoopArg::LFnewloopagain);
@@ -201,25 +199,6 @@ public:
             if (container.queryOwner().isGlobal())
                 global = true;
         }
-        if (!global)
-            return;
-        initLoopResults(1);
-    }
-    void initLoopResults(unsigned loopCounter)
-    {
-        unsigned doLoopCounter = (flags & IHThorLoopArg::LFcounter) ? loopCounter : 0;
-        unsigned doLoopAgain = (flags & IHThorLoopArg::LFnewloopagain) ? helper->loopAgainResult() : 0;
-        ownedResults.setown(queryGraph().createThorGraphResults(3)); // will not be cleared until next sync
-        // ensures remote results are available, via owning activity (i.e. this loop act)
-        // so that when the master/slave result parts are fetched, it will retreive from the act, not the (already cleaed) graph localresults
-        ownedResults->setOwner(container.queryId());
-
-        boundGraph->prepareLoopResults(*this, ownedResults);
-        if (doLoopCounter) // cannot be 0
-            boundGraph->prepareCounterResult(*this, ownedResults, loopCounter, 2);
-        if (doLoopAgain) // cannot be 0
-            boundGraph->prepareLoopAgainResult(*this, ownedResults, helper->loopAgainResult());
-
     }
     void process()
     {
@@ -235,9 +214,23 @@ public:
             {
                 if (sync(loopCounter))
                     break;
-                if (loopCounter > 1)
-                    initLoopResults(loopCounter);
-                boundGraph->execute(*this, (flags & IHThorLoopArg::LFcounter)?loopCounter:0, ownedResults, (IRowWriterMultiReader *)NULL, 0, extractBuilder.size(), extractBuilder.getbytes());
+
+                // NB: This is exactly the same as the slave implementation up until the execute().
+                IThorBoundLoopGraph *boundGraph = queryContainer().queryLoopGraph();
+                unsigned condLoopCounter = (flags & IHThorLoopArg::LFcounter) ? loopCounter : 0;
+                unsigned loopAgain = (flags & IHThorLoopArg::LFnewloopagain) ? helper->loopAgainResult() : 0;
+                ownedResults.setown(queryGraph().createThorGraphResults(3)); // will not be cleared until next sync
+                // ensures remote results are available, via owning activity (i.e. this loop act)
+                // so that when the master/slave result parts are fetched, it will retreive from the act, not the (already cleaed) graph localresults
+                ownedResults->setOwner(container.queryId());
+
+                boundGraph->prepareLoopResults(*this, ownedResults);
+                if (condLoopCounter)
+                    boundGraph->prepareCounterResult(*this, ownedResults, condLoopCounter, 2);
+                if (loopAgain) // cannot be 0
+                    boundGraph->prepareLoopAgainResult(*this, ownedResults, loopAgain);
+
+                boundGraph->execute(*this, condLoopCounter, ownedResults, (IRowWriterMultiReader *)NULL, 0, extractBuilder.size(), extractBuilder.getbytes());
                 ++loopCounter;
                 if (flags & IHThorLoopArg::LFnewloopagain)
                 {

+ 7 - 7
thorlcr/activities/loop/thloopslave.cpp

@@ -369,20 +369,20 @@ public:
                 loopPending->flush();
 
                 IThorBoundLoopGraph *boundGraph = queryContainer().queryLoopGraph();
-                unsigned doLoopCounter = (flags & IHThorLoopArg::LFcounter) ? loopCounter:0;
-                unsigned doLoopAgain = (flags & IHThorLoopArg::LFnewloopagain) ? helper->loopAgainResult() : 0;
+                unsigned condLoopCounter = (flags & IHThorLoopArg::LFcounter) ? loopCounter:0;
+                unsigned loopAgain = (flags & IHThorLoopArg::LFnewloopagain) ? helper->loopAgainResult() : 0;
                 ownedResults.setown(queryGraph().createThorGraphResults(3));
                 // ensures remote results are available, via owning activity (i.e. this loop act)
                 // so that when aggregate result is fetched from the master, it will retrieve from the act, not the (already cleaned) graph localresults
                 ownedResults->setOwner(container.queryId());
 
                 boundGraph->prepareLoopResults(*this, ownedResults);
-                if (doLoopCounter) // cannot be 0
-                    boundGraph->prepareCounterResult(*this, ownedResults, loopCounter, 2);
-                if (doLoopAgain) // cannot be 0
-                    boundGraph->prepareLoopAgainResult(*this, ownedResults, helper->loopAgainResult());
+                if (condLoopCounter) // cannot be 0
+                    boundGraph->prepareCounterResult(*this, ownedResults, condLoopCounter, 2);
+                if (loopAgain) // cannot be 0
+                    boundGraph->prepareLoopAgainResult(*this, ownedResults, loopAgain);
 
-                boundGraph->execute(*this, doLoopCounter, ownedResults, loopPending.getClear(), loopPendingCount, extractBuilder.size(), extractBuilder.getbytes());
+                boundGraph->execute(*this, condLoopCounter, ownedResults, loopPending.getClear(), loopPendingCount, extractBuilder.size(), extractBuilder.getbytes());
 
                 Owned<IThorResult> result0 = ownedResults->getResult(0);
                 curInput.setown(result0->getRowStream());