瀏覽代碼

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

Conflicts:
	dali/ft/daftformat.cpp

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 11 年之前
父節點
當前提交
fdd6171fc4
共有 33 個文件被更改,包括 556 次插入321 次删除
  1. 12 0
      build-config.h.cmake
  2. 1 0
      common/fileview2/CMakeLists.txt
  3. 75 18
      dali/ft/daftformat.cpp
  4. 1 1
      dali/ft/daftformat.ipp
  5. 23 5
      docs/ECLLanguageReference/ECLR_mods/Basics-AttributeVisibility.xml
  6. 21 11
      docs/ECLLanguageReference/ECLR_mods/BltInFunc-DENORMALIZE.xml
  7. 6 1
      docs/ECLLanguageReference/ECLR_mods/ParSppt-PARSPattrn.xml
  8. 42 12
      docs/ECLLanguageReference/ECLR_mods/SpecStruc-FuncTionMacro.xml
  9. 32 3
      ecl/ecl-bundle/ecl-bundle.cpp
  10. 4 3
      ecl/hql/hql.hpp
  11. 2 0
      ecl/hql/hqlerrors.hpp
  12. 1 1
      ecl/hql/hqlfold.cpp
  13. 3 3
      ecl/hql/hqlgram.y
  14. 2 2
      ecl/hql/hqlgram2.cpp
  15. 1 1
      ecl/hql/hqlopt.cpp
  16. 13 4
      ecl/hql/hqlutil.cpp
  17. 1 1
      ecl/hql/hqlutil.hpp
  18. 1 1
      ecl/hqlcpp/hqlttcpp.cpp
  19. 13 51
      esp/files/scripts/DFUQueryWidget.js
  20. 109 0
      esp/files/scripts/FilterDropDownWidget.js
  21. 24 67
      esp/files/scripts/GetDFUWorkunitsWidget.js
  22. 28 68
      esp/files/scripts/WUQueryWidget.js
  23. 12 22
      esp/files/templates/DFUQueryWidget.html
  24. 17 0
      esp/files/templates/FilterDropDownWidget.html
  25. 6 16
      esp/files/templates/GetDFUWorkunitsWidget.html
  26. 2 2
      esp/files/templates/HPCCPlatformFilesWidget.html
  27. 14 24
      esp/files/templates/WUQueryWidget.html
  28. 1 0
      esp/services/ws_dfu/CMakeLists.txt
  29. 1 0
      esp/services/ws_workunits/CMakeLists.txt
  30. 12 3
      plugins/unicodelib/unicodelib.cpp
  31. 20 0
      testing/ecl/issue9879.ecl
  32. 45 0
      testing/ecl/key/issue9879.xml
  33. 11 1
      thorlcr/activities/hashdistrib/thhashdistribslave.cpp

+ 12 - 0
build-config.h.cmake

@@ -82,6 +82,18 @@
     #cmakedefine BUILD_TAG "${BUILD_TAG}"
 #endif
 
+#ifndef BUILD_VERSION_MAJOR
+    #define BUILD_VERSION_MAJOR ${HPCC_MAJOR}
+#endif
+
+#ifndef BUILD_VERSION_MINOR
+    #define BUILD_VERSION_MINOR ${HPCC_MINOR}
+#endif
+
+#ifndef BUILD_VERSION_POINT
+    #define BUILD_VERSION_POINT ${HPCC_POINT}
+#endif
+
 #ifndef BASE_BUILD_TAG
     #cmakedefine BASE_BUILD_TAG "${BASE_BUILD_TAG}"
 #endif

+ 1 - 0
common/fileview2/CMakeLists.txt

@@ -60,6 +60,7 @@ set (    SRCS
     )
 
 include_directories ( 
+         ${CMAKE_BINARY_DIR}/oss
          ./../../system/mp 
          ./../../system/jhtree 
          ./../dllserver 

+ 75 - 18
dali/ft/daftformat.cpp

@@ -566,6 +566,10 @@ CCsvPartitioner::CCsvPartitioner(const FileFormat & _format) : CInputBasePartiti
     addActionList(matcher, format.separate.get() ? format.separate.get() : "\\,", SEPARATOR, &maxElementLength);
     addActionList(matcher, format.quote.get() ? format.quote.get() : "'", QUOTE, &maxElementLength);
     addActionList(matcher, format.terminate.get() ? format.terminate.get() : "\\n,\\r\\n", TERMINATOR, &maxElementLength);
+    const char * escape = format.escape.get();
+    if (escape && *escape)
+        addActionList(matcher,  escape, ESCAPE, &maxElementLength);
+
     matcher.queryAddEntry(1, " ", WHITESPACE);
     matcher.queryAddEntry(1, "\t", WHITESPACE);
 }
@@ -574,11 +578,14 @@ size32_t CCsvPartitioner::getSplitRecordSize(const byte * start, unsigned maxToR
 {
     //more complicated processing of quotes etc....
     unsigned quote = 0;
+    unsigned quoteToStrip = 0;
     const byte * cur = start;
     const byte * end = start + maxToRead;
-    const byte * startOfColumn = cur;
-
+    const byte * firstGood = start;
+    const byte * lastGood = start;
     const byte * last = start;
+    bool lastEscape = false;
+
     while (cur != end)
     {
         unsigned matchLen;
@@ -587,53 +594,103 @@ size32_t CCsvPartitioner::getSplitRecordSize(const byte * start, unsigned maxToR
         {
         case NONE:
             cur++;          // matchLen == 0;
+            lastGood = cur;
             break;
         case WHITESPACE:
             //Skip leading whitepace
-            if (!quote&&(cur == startOfColumn))
+            if (quote)
+                lastGood = cur+matchLen;
+            else if (cur == firstGood)
             {
-                startOfColumn = cur+matchLen;
+                firstGood = cur+matchLen;
+                lastGood = cur+matchLen;
             }
             break;
         case SEPARATOR:
+            // Quoted separator
             if (quote == 0)
             {
-                startOfColumn = cur + matchLen;     // NB: Can write one past end.
+                lastEscape = false;
+                quoteToStrip = 0;
+                firstGood = cur + matchLen;
             }
+            lastGood = cur+matchLen;
             break;
         case TERMINATOR:
-            if (quote == 0)
+            if (quote == 0) // Is this a good idea? Means a mismatched quote is not fixed by EOL
             {
-                if(processFullBuffer)
-                {
-                    last = cur + matchLen;
-                }
-                else
-                {
-                    return cur + matchLen - start;
-                }
+               if (processFullBuffer)
+               {
+                   last = cur + matchLen;
+                   // Reset to process a new record
+                   lastEscape = false;
+                   quoteToStrip = 0;
+                   firstGood = cur + matchLen;
+               }
+               else
+               {
+                   return (size32_t)(cur + matchLen - start);
+               }
             }
+            lastGood = cur+matchLen;
             break;
         case QUOTE:
+            // Quoted quote
             if (quote == 0)
             {
-                if (cur == startOfColumn)
+                if (cur == firstGood)
                 {
                     quote = match;
-                    startOfColumn = cur+matchLen;
+                    firstGood = cur+matchLen;
                 }
+                lastGood = cur+matchLen;
             }
             else
             {
                 if (quote == match)
-                    quote = 0;
+                {
+                    const byte * next = cur + matchLen;
+                    //Check for double quotes
+                    if ((next != end))
+                    {
+                        unsigned nextMatchLen;
+                        unsigned nextMatch = matcher.getMatch((size32_t)(end-next), (const char *)next, nextMatchLen);
+                        if (nextMatch == quote)
+                        {
+                            quoteToStrip = quote;
+                            matchLen += nextMatchLen;
+                            lastGood = cur+matchLen;
+                        }
+                        else
+                            quote = 0;
+                    }
+                    else
+                        quote = 0;
+                }
+                else
+                    lastGood = cur+matchLen;
             }
             break;
+        case ESCAPE:
+            lastEscape = true;
+            lastGood = cur+matchLen;
+            // If this escape is at the end, proceed to field range
+            if (lastGood == end)
+                break;
+
+            // Skip escape and ignore the next match
+            cur += matchLen;
+            match = matcher.getMatch((size32_t)(end-cur), (const char *)cur, matchLen);
+            if ((match & 255) == NONE)
+                matchLen = 1;
+            lastGood += matchLen;
+            break;
+
         }
         cur += matchLen;
     }
 
-    if(processFullBuffer && (last != start))
+    if (processFullBuffer && (last != start))
     {
         return last - start;
     }

+ 1 - 1
dali/ft/daftformat.ipp

@@ -239,7 +239,7 @@ protected:
     }
 
 protected:
-    enum { NONE=0, SEPARATOR=1, TERMINATOR=2, WHITESPACE=3, QUOTE=4 };
+    enum { NONE=0, SEPARATOR=1, TERMINATOR=2, WHITESPACE=3, QUOTE=4, ESCAPE=5 };
     unsigned        maxElementLength;
     FileFormat      format;
     StringMatcher   matcher;

+ 23 - 5
docs/ECLLanguageReference/ECLR_mods/Basics-AttributeVisibility.xml

@@ -84,7 +84,7 @@ EXPORT Definition3 := $.Definition2 + 5;
     are referenced by their definition name alone; no qualification is
     needed.</para>
 
-    <programlisting>//then inside the Definition4.ecl file (in the sam efolder as Definition2) you have:
+    <programlisting>//then inside the Definition4.ecl file (in the same folder as Definition2) you have:
 IMPORT $;  
    //makes definitions from the current module available to this code, as needed
 
@@ -100,9 +100,27 @@ LocalDef2 := Definition4 + LocalDef;
   //or SHARED definition in the file are meaningless 
   //since they can never be used by anything
 </programlisting>
-  </sect2>
 
-  <para>See Also: <link linkend="IMPORT">IMPORT</link>, <link
-  linkend="EXPORT">EXPORT</link>, <link linkend="SHARED">SHARED</link>, <link
-  linkend="MODULE_Structure">MODULE</link></para>
+    <para>The <emphasis role="bold">LOCAL</emphasis><indexterm>
+        <primary>LOCAL</primary>
+      </indexterm> keyword is valid for use within any nested structure, but
+    most useful within a FUNCTIONMACRO structure to clearly identify that the
+    scope of a definition is limited to the code generated within the
+    FUNCTIONMACRO.</para>
+
+    <programlisting>AddOne(num) := FUNCTIONMACRO
+  LOCAL numPlus := num + 1;
+  RETURN numPlus;
+ENDMACRO;
+
+numPlus := 'this is a syntax error without LOCAL in the FUNCTIONMACRO';
+numPlus;
+AddOne(5);
+</programlisting>
+
+    <para>See Also: <link linkend="IMPORT">IMPORT</link>, <link
+    linkend="EXPORT">EXPORT</link>, <link linkend="SHARED">SHARED</link>,
+    <link linkend="MODULE_Structure">MODULE</link>, <link
+    linkend="FUNCTIONMACRO_Structure">FUNCTIONMACRO</link></para>
+  </sect2>
 </sect1>

+ 21 - 11
docs/ECLLanguageReference/ECLR_mods/BltInFunc-DENORMALIZE.xml

@@ -81,8 +81,8 @@
 
             <entry>Specifies grouping the <emphasis>childrecset</emphasis>
             records based on the join condition so all the related child
-            records are passed as a dataset parameter to the transform.
-            </entry>
+            records are passed as a dataset parameter to the
+            transform.</entry>
           </row>
 
           <row>
@@ -108,6 +108,11 @@
   order in which the <emphasis>childrecset</emphasis> records are sent to the
   <emphasis>transform</emphasis> is undefined.</para>
 
+  <para>Because DENORMALIZE is basically a specialized form of JOIN, the
+  various join types (LEFT OUTER, RIGHT OUTER, FULL OUTER, LEFT ONLY, RIGHT
+  ONLY, FULL ONLY) are also available for use on DENORMALIZE and act just as
+  they do with JOIN.</para>
+
   <sect2 id="TRANSFORM_Function_Requirements_Denormalize">
     <title>DENORMALIZE TRANSFORM Function Requirements</title>
 
@@ -136,10 +141,11 @@
     <emphasis>transform</emphasis> function must be a record set of the same
     format as the LEFT record.</para>
 
-    <para>Example:</para>
+    <para><emphasis role="bold">Example:</emphasis></para>
+
+    <para>Form 1 example:</para>
 
-    <programlisting>//Form 1 example:
-NormRec := RECORD
+    <programlisting>NormRec := RECORD
   STRING20  thename;
   STRING20  addr;
 END;
@@ -172,9 +178,11 @@ DeNormedRecs := DENORMALIZE(NamesTable, NormAddrs,
                             LEFT.thename = RIGHT.thename,
                             DeNormThem(LEFT,RIGHT,COUNTER));
 OUTPUT(DeNormedRecs);
+</programlisting>
 
-//Form 2 example:
-NormRec := RECORD
+    <para>Form 2 example:</para>
+
+    <programlisting>NormRec := RECORD
   STRING20  thename;
   STRING20  addr;
 END;
@@ -203,9 +211,11 @@ DeNormedRecs := DENORMALIZE(NamesTable, NormAddrs,
                            GROUP,
                            DeNormThem(LEFT,ROWS(RIGHT)));
 OUTPUT(DeNormedRecs);
+</programlisting>
+
+    <para>NOSORT example:</para>
 
-// NOSORT example
-MyRec := RECORD
+    <programlisting>MyRec := RECORD
   STRING1 Value1;
   STRING1 Value2;
 END;
@@ -236,8 +246,8 @@ OUTPUT(DeNormedRecs);
  */
 </programlisting>
 
-    <para>See Also: <link linkend="TRANSFORM_Structure">TRANSFORM
-    Structure</link>, <link
+    <para>See Also: <link linkend="JOIN">JOIN</link>, <link
+    linkend="TRANSFORM_Structure">TRANSFORM Structure</link>, <link
     linkend="RECORD_Structure">RECORD Structure</link>, <link
     linkend="NORMALIZE">NORMALIZE</link></para>
   </sect2>

+ 6 - 1
docs/ECLLanguageReference/ECLR_mods/ParSppt-PARSPattrn.xml

@@ -409,7 +409,12 @@ the following supported syntax elements:
  [0-9abcdef]    A set of characters
                         (may use ^ for exclusion list)
  (?=…) (?!...)     Look ahead assertion
- (?&lt;=…) (?&lt;!...)   Look behind assertion</programlisting><!--*** Note this and the following row entries have been monospace optimized for PDF/HTML*** --></entry>
+ (?&lt;=…) (?&lt;!...)   Look behind assertion
+
+Escape sequences can be used to define UNICODE Character ranges. The encoding is UTF-16 Big Endian.  
+For example:
+PATTERN AnyChar    := PATTERN(U'[\u0001-\u7fff]');
+</programlisting><!--*** Note this and the following row entries have been monospace optimized for PDF/HTML*** --></entry>
           </row>
 
           <row>

+ 42 - 12
docs/ECLLanguageReference/ECLR_mods/SpecStruc-FuncTionMacro.xml

@@ -82,16 +82,25 @@
   </informaltable>
 
   <para>The <emphasis role="bold">FUNCTIONMACRO </emphasis>structure is a code
-  generation tool, like the MACRO structure, coupled with code encapsulation
-  benefits of the FUNCTION structure. This means that #UNIQUENAME is not
-  necessary to prevent definition name clashes -- the definitions in the
-  <emphasis>code</emphasis> are local within the FUNCTIONMACRO.</para>
-
-  <para>One additional advantage the FUNCTIONMACRO has over the MACRO
-  structure is that it may be called in an expression context, just like a
-  FUNCTION would be.</para>
-
-  <para>Example:</para>
+  generation tool, like the MACRO structure, coupled with the code
+  encapsulation benefits of the FUNCTION structure. One advantage the
+  FUNCTIONMACRO has over the MACRO structure is that it may be called in an
+  expression context, just like a FUNCTION would be.</para>
+
+  <para>Unlike the MACRO structure, #UNIQUENAME is not necessary to prevent
+  internal definition name clashes when the FUNCTIONMACRO is used multiple
+  times within the same visibility scope. However, the <emphasis
+  role="bold">LOCAL</emphasis> keyword must be explicitly used within the
+  FUNCTIONMACRO if a definition name in its <emphasis>code</emphasis> may also
+  have been defined outside the FUNCTIONMACRO and within the same visibility
+  scope -- LOCAL clearly identifies that the definition is limited to the
+  <emphasis>code</emphasis> within the FUNCTIONMACRO.</para>
+
+  <para><emphasis role="bold">Example:</emphasis></para>
+
+  <para>This example demonstrates the FUNCTIONMACRO used in an expression
+  context. It also shows how the FUNCTIONMACRO may be called multiple times
+  without name clashes from its internal definitions:</para>
 
   <programlisting>EXPORT Field_Population(infile,infield,compareval) := FUNCTIONMACRO
   c1 := COUNT(infile(infield=compareval));
@@ -106,7 +115,28 @@ ds1 := dataset([{'M'},{'M'},{'M'},{''},{''},{'M'},{''},{'M'},{'M'},{''}],{STRING
 ds2 := dataset([{''},{'M'},{'M'},{''},{''},{'M'},{''},{''},{'M'},{''}],{STRING1 Gender});
 
 OUTPUT(Field_Population(ds1,Gender,''));
-OUTPUT(Field_Population(ds2,Gender,''));        </programlisting>
+OUTPUT(Field_Population(ds2,Gender,''));</programlisting>
+
+  <para>This example demonstrates use of the LOCAL keyword to prevent name
+  clashes with external definitions within the same visibility scope as the
+  FUNCTIONMACRO:</para>
+
+  <programlisting>numPlus := 'this creates a syntax error without LOCAL in the FUNCTIONMACRO';
+AddOne(num) := FUNCTIONMACRO
+  LOCAL numPlus := num + 1;   //LOCAL required here
+  RETURN numPlus;
+ENDMACRO;
+
+AddTwo(num) := FUNCTIONMACRO
+  LOCAL numPlus := num + 2;   //LOCAL required here
+  RETURN numPlus;
+ENDMACRO;
+
+numPlus;
+AddOne(5);
+AddTwo(8);</programlisting>
 
-  <para>See Also: <link linkend="FUNCTION_Structure">FUNCTION Structure</link>, <link linkend="MACRO_Structure">MACRO Structure</link></para>
+  <para>See Also: <link linkend="FUNCTION_Structure">FUNCTION
+  Structure</link>, <link linkend="MACRO_Structure">MACRO
+  Structure</link></para>
 </sect1>

+ 32 - 3
ecl/ecl-bundle/ecl-bundle.cpp

@@ -197,6 +197,20 @@ static void extractValueFromEnvOutput(StringBuffer &path, const char *specs, con
     }
 }
 
+bool directoryContainsBundleFile(IFile *dir)
+{
+    Owned<IDirectoryIterator> files = dir->directoryFiles(NULL, false, false);
+    ForEach(*files)
+    {
+        IFile *thisFile = &files->query();
+        StringBuffer fileName;
+        splitFilename(thisFile->queryFilename(), NULL, NULL, &fileName, &fileName);
+        if (stricmp(fileName.str(), "bundle.ecl")==0)
+            return true;
+    }
+    return false;
+}
+
 void recursiveRemoveDirectory(IFile *dir, bool isDryRun)
 {
     Owned<IDirectoryIterator> files = dir->directoryFiles(NULL, false, true);
@@ -280,10 +294,19 @@ public:
             Owned<IFile> bundleFile = createIFile(bundle);
             if (bundleFile->exists())
             {
+                StringBuffer cleanedParam(bundle);
+                removeTrailingPathSepChar(cleanedParam);
                 StringBuffer drive, path;
-                splitFilename(bundle, &drive, &path, &bundleName, NULL, true);
+                splitFilename(cleanedParam, &drive, &path, &bundleName, NULL, true);
                 if (bundleFile->isDirectory())
-                    eclOpts.appendf(" -I%s", bundle);
+                {
+                    // Distinguish a directory containing a single file (zipped single module case) from a
+                    // directory containing multiple exported files. Somehow.
+                    if (directoryContainsBundleFile(bundleFile))
+                        eclOpts.appendf(" -I%s%s", drive.str(), path.str());
+                    else
+                        eclOpts.appendf(" -I%s", bundle);
+                }
                 else if (drive.length() + path.length())
                     eclOpts.appendf(" -I%s%s", drive.str(), path.str());
                 else
@@ -878,7 +901,7 @@ protected:
     }
     bool isFromFile() const
     {
-        // If a supplied bundle id contains pathsep or ., assume a filename is being supplied
+        // If a supplied bundle id contains pathsep or ., assume a filename or directory is being supplied
         return strchr(optBundle, PATHSEPCHAR) != NULL || strchr(optBundle, '.') != NULL;
     }
     StringAttr optBundle;
@@ -1160,6 +1183,12 @@ public:
                 throw MakeStringException(0, "Cannot create bundle version directory %s", versionPath.str());
             if (bundleFile->isDirectory() == foundYes) // could also be an archive, acting as a directory
             {
+                if (directoryContainsBundleFile(bundleFile))
+                {
+                    versionPath.append(PATHSEPCHAR).append(bundle->queryCleanName());
+                    if (!optDryRun && !recursiveCreateDirectory(versionPath))
+                        throw MakeStringException(0, "Cannot create bundle version directory %s", versionPath.str());
+                }
                 copyDirectory(bundleFile, versionPath);
             }
             else

+ 4 - 3
ecl/hql/hql.hpp

@@ -27,6 +27,7 @@
 #define HQL_API
 #endif
 #include "hqlatoms.hpp"
+#include "build-config.h"
 
 #define stringify(x) # x
 #define estringify(x) stringify(x)
@@ -47,9 +48,9 @@ Relevant changes include
 
 */
 
-#define LANGUAGE_VERSION_MAJOR      4
-#define LANGUAGE_VERSION_MINOR      0
-#define LANGUAGE_VERSION_SUB        0
+#define LANGUAGE_VERSION_MAJOR      BUILD_VERSION_MAJOR
+#define LANGUAGE_VERSION_MINOR      BUILD_VERSION_MINOR
+#define LANGUAGE_VERSION_SUB        BUILD_VERSION_POINT
 
 #define LANGUAGE_VERSION   estringify(LANGUAGE_VERSION_MAJOR) "." estringify(LANGUAGE_VERSION_MINOR) "." estringify(LANGUAGE_VERSION_SUB)
 

+ 2 - 0
ecl/hql/hqlerrors.hpp

@@ -464,6 +464,7 @@
 #define HQLERR_UnexpectedType                   3126
 #define HQLERR_PayloadMismatch                  3127
 #define HQLERR_MemberXContainsVirtualRef        3128
+#define HQLERR_FieldHasNoDefaultValue           3129
 
 #define HQLERR_DedupFieldNotFound_Text          "Field removed from dedup could not be found"
 #define HQLERR_CycleWithModuleDefinition_Text   "Module definition contain an illegal cycle/recursive definition %s"
@@ -494,6 +495,7 @@
 #define HQLWRN_CouldNotConstantFoldIf_Text      "Could not constant fold the condition on a IFBLOCK for a inline table"
 #define HQLERR_PayloadMismatch_Text             "Mismatched => in inline dictionary definition"
 #define HQLERR_MemberXContainsVirtualRef_Text   "Member %s contains virtual references but not supported as virtual"
+#define HQLERR_FieldHasNoDefaultValue_Text      "Field '%s' doesn't have a defined value"
 
 /* parser error */
 #define ERR_PARSER_CANNOTRECOVER    3005  /* The parser can not recover from previous error(s) */

+ 1 - 1
ecl/hql/hqlfold.cpp

@@ -4811,7 +4811,7 @@ IHqlExpression * CExprFolderTransformer::doFoldTransformed(IHqlExpression * unfo
             {
                 ECLlocation dummyLocation(0, 0, 0, NULL);
                 ThrowingErrorReceiver errorReporter;
-                OwnedHqlExpr inlineTable = convertTempTableToInlineTable(&errorReporter, dummyLocation, expr);
+                OwnedHqlExpr inlineTable = convertTempTableToInlineTable(errorReporter, dummyLocation, expr);
                 if (expr != inlineTable)
                     return inlineTable.getClear();
             }

+ 3 - 3
ecl/hql/hqlgram.y

@@ -7204,7 +7204,7 @@ simpleDictionary
                         {
                             HqlExprArray values;  // Empty list
                             OwnedHqlExpr table = createDataset(no_temptable, createValue(no_recordlist, NULL, values), $6.getExpr());
-                            OwnedHqlExpr ds = convertTempTableToInlineTable(parser->errorHandler, $4.pos, table);
+                            OwnedHqlExpr ds = convertTempTableToInlineTable(*parser->errorHandler, $4.pos, table);
                             $$.setExpr(createDictionary(no_createdictionary, ds.getClear()), $1);
                         }
     | DICTIONARY '(' '[' beginList inlineDatasetValueList ']' ',' recordDef ')'
@@ -7212,7 +7212,7 @@ simpleDictionary
                             HqlExprArray values;
                             parser->endList(values);
                             OwnedHqlExpr table = createDataset(no_temptable, createValue(no_recordlist, NULL, values), $8.getExpr());
-                            OwnedHqlExpr ds = convertTempTableToInlineTable(parser->errorHandler, $5.pos, table);
+                            OwnedHqlExpr ds = convertTempTableToInlineTable(*parser->errorHandler, $5.pos, table);
                             $$.setExpr(createDictionary(no_createdictionary, ds.getClear()), $1);
                         }
     | '(' dictionary  ')'  {
@@ -8486,7 +8486,7 @@ simpleDataSet
                             HqlExprArray values;
                             parser->endList(values);
                             OwnedHqlExpr table = createDataset(no_temptable, createValue(no_recordlist, NULL, values), $8.getExpr());
-                            $$.setExpr(convertTempTableToInlineTable(parser->errorHandler, $5.pos, table));
+                            $$.setExpr(convertTempTableToInlineTable(*parser->errorHandler, $5.pos, table));
                             $$.setPosition($1);
                         }
     | DATASET '(' '[' beginList transformList ']' ')'

+ 2 - 2
ecl/hql/hqlgram2.cpp

@@ -5282,7 +5282,7 @@ IHqlExpression * HqlGram::createDatasetFromList(attribute & listAttr, attribute
     {
         OwnedHqlExpr list = createValue(no_null);
         OwnedHqlExpr table = createDataset(no_temptable, LINK(list), record.getClear());
-        return convertTempTableToInlineTable(errorHandler, listAttr.pos, table);
+        return convertTempTableToInlineTable(*errorHandler, listAttr.pos, table);
         return createDataset(no_null, LINK(record));
     }
 
@@ -5323,7 +5323,7 @@ IHqlExpression * HqlGram::createDatasetFromList(attribute & listAttr, attribute
         reportError(ERR_RECORD_NOT_MATCH_SET, recordAttr, "The field in the record does not match the type of the set elements");
     
     OwnedHqlExpr table = createDataset(no_temptable, LINK(list), record.getClear());
-    return convertTempTableToInlineTable(errorHandler, listAttr.pos, table);
+    return convertTempTableToInlineTable(*errorHandler, listAttr.pos, table);
 }
 
 

+ 1 - 1
ecl/hql/hqlopt.cpp

@@ -2492,7 +2492,7 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme
             {
                 ECLlocation dummyLocation(0, 0, 0, NULL);
                 ThrowingErrorReceiver errorReporter;
-                OwnedHqlExpr inlineTable = convertTempTableToInlineTable(&errorReporter, dummyLocation, transformed);
+                OwnedHqlExpr inlineTable = convertTempTableToInlineTable(errorReporter, dummyLocation, transformed);
                 if (transformed != inlineTable)
                     return inlineTable.getClear();
             }

+ 13 - 4
ecl/hql/hqlutil.cpp

@@ -5596,7 +5596,7 @@ IHqlExpression * convertTempRowToCreateRow(IErrorReceiver * errors, ECLlocation
     return expr->cloneAllAnnotations(ret);
 }
 
-static IHqlExpression * convertTempTableToInline(IErrorReceiver * errors, ECLlocation & location, IHqlExpression * expr)
+static IHqlExpression * convertTempTableToInline(IErrorReceiver & errors, ECLlocation & location, IHqlExpression * expr)
 {
     IHqlExpression * oldValues = expr->queryChild(0);
     IHqlExpression * record = expr->queryChild(1);
@@ -5609,7 +5609,7 @@ static IHqlExpression * convertTempTableToInline(IErrorReceiver * errors, ECLloc
     if ((valueOp != no_recordlist) && (valueOp != no_list))
         return LINK(expr);
 
-    TempTableTransformer transformer(errors, location);
+    TempTableTransformer transformer(&errors, location);
     HqlExprArray transforms;
     ForEachChild(idx, values)
     {
@@ -5623,7 +5623,16 @@ static IHqlExpression * convertTempTableToInline(IErrorReceiver * errors, ECLloc
             {
                 IHqlExpression * field = cur->queryChild(idx);
                 if (field->getOperator() == no_field)
-                    row.append(*LINK(field->queryChild(0)));
+                {
+                    IHqlExpression * value = queryRealChild(field, 0);
+                    if (value)
+                        row.append(*LINK(value));
+                    else
+                    {
+                        VStringBuffer msg(HQLERR_FieldHasNoDefaultValue_Text, field->queryName()->str());
+                        errors.reportError(HQLERR_FieldHasNoDefaultValue, msg.str(), location.sourcePath->str(), location.lineno, location.column, location.position);
+                    }
+                }
             }
             cur.setown(createValue(no_rowvalue, makeNullType(), row));
         }
@@ -5637,7 +5646,7 @@ static IHqlExpression * convertTempTableToInline(IErrorReceiver * errors, ECLloc
     return expr->cloneAllAnnotations(ret);
 }
 
-IHqlExpression * convertTempTableToInlineTable(IErrorReceiver * errors, ECLlocation & location, IHqlExpression * expr)
+IHqlExpression * convertTempTableToInlineTable(IErrorReceiver & errors, ECLlocation & location, IHqlExpression * expr)
 {
     return convertTempTableToInline(errors, location, expr);
 }

+ 1 - 1
ecl/hql/hqlutil.hpp

@@ -461,7 +461,7 @@ extern HQL_API IHqlExpression * createSelectMapRow(IErrorReceiver * errors, ECLl
 extern HQL_API IHqlExpression * createINDictExpr(IErrorReceiver * errors, ECLlocation & location, IHqlExpression *expr, IHqlExpression *dict);
 extern HQL_API IHqlExpression *createINDictRow(IErrorReceiver * errors, ECLlocation & location, IHqlExpression *row, IHqlExpression *dict);
 extern HQL_API IHqlExpression * convertTempRowToCreateRow(IErrorReceiver * errors, ECLlocation & location, IHqlExpression * expr);
-extern HQL_API IHqlExpression * convertTempTableToInlineTable(IErrorReceiver * errors, ECLlocation & location, IHqlExpression * expr);
+extern HQL_API IHqlExpression * convertTempTableToInlineTable(IErrorReceiver & errors, ECLlocation & location, IHqlExpression * expr);
 extern HQL_API void setPayloadAttribute(HqlExprArray &args);
 
 extern HQL_API bool areTypesComparable(ITypeInfo * leftType, ITypeInfo * rightType);

+ 1 - 1
ecl/hqlcpp/hqlttcpp.cpp

@@ -10837,7 +10837,7 @@ IHqlExpression * HqlTreeNormalizer::transformTempTable(IHqlExpression * expr)
 {
     ECLlocation dummyLocation(0, 0, 0, NULL);
     AbortingErrorReceiver errorReporter(errors);
-    OwnedHqlExpr inlineTable = convertTempTableToInlineTable(&errorReporter, dummyLocation, expr);
+    OwnedHqlExpr inlineTable = convertTempTableToInlineTable(errorReporter, dummyLocation, expr);
     if (expr != inlineTable)
         return transform(inlineTable);
 

+ 13 - 51
esp/files/scripts/DFUQueryWidget.js

@@ -50,6 +50,7 @@ define([
     "hpcc/SFDetailsWidget",
     "hpcc/DFUWUDetailsWidget",
     "hpcc/TargetSelectWidget",
+    "hpcc/FilterDropDownWidget",
 
     "dojo/text!../templates/DFUQueryWidget.html",
 
@@ -69,7 +70,7 @@ define([
 ], function (declare, lang, arrayUtil, dom, domAttr, domConstruct, domClass, domForm, date, on,
                 registry, Dialog, Menu, MenuItem, MenuSeparator, PopupMenuItem, Textarea,
                 Grid, Keyboard, Selection, selector, ColumnResizer, DijitRegistry, Pagination,
-                _TabContainerWidget, WsDfu, ESPUtil, ESPLogicalFile, ESPDFUWorkunit, LFDetailsWidget, SFDetailsWidget, DFUWUDetailsWidget, TargetSelectWidget,
+                _TabContainerWidget, WsDfu, ESPUtil, ESPLogicalFile, ESPDFUWorkunit, LFDetailsWidget, SFDetailsWidget, DFUWUDetailsWidget, TargetSelectWidget, FilterDropDownWidget,
                 template) {
     return declare("DFUQueryWidget", [_TabContainerWidget, ESPUtil.FormHelper], {
         templateString: template,
@@ -78,11 +79,12 @@ define([
         workunitsTab: null,
         workunitsGrid: null,
 
-        validateDialog: null,
+        filter: null,
 
         postCreate: function (args) {
             this.inherited(arguments);
             this.workunitsTab = registry.byId(this.id + "_Workunits");
+            this.filter = registry.byId(this.id + "Filter");
             this.clusterTargetSelect = registry.byId(this.id + "ClusterTargetSelect");
             this.desprayTargetSelect = registry.byId(this.id + "DesprayTargetSelect");
         },
@@ -178,20 +180,6 @@ define([
             registry.byId(this.id + "DesprayDropDown").closeDropDown();
         },
 
-        _onFilterApply: function (event) {
-            registry.byId(this.id + "FilterDropDown").closeDropDown();
-            if (this.hasFilter()) {
-                this.applyFilter();
-            } else {
-                this.validateDialog.show();
-            }
-        },
-
-        _onFilterClear: function(event) {
-            this.clearFilter();
-            this.applyFilter();
-        },
-
         _onRowDblClick: function (item) {
             var wuTab = this.ensureLFPane(this.id + "_" + item.Name, item);
             this.selectChild(wuTab);
@@ -218,24 +206,8 @@ define([
         },
 
         //  Implementation  ---
-        clearFilter: function () {
-            arrayUtil.forEach(registry.byId(this.id + "FilterForm").getDescendants(), function (item, idx) {
-                item.set('value', null);
-            });
-        },
-
-        hasFilter: function () {
-            var filter = domForm.toObject(this.id + "FilterForm");
-            for (var key in filter) {
-                if (filter[key] != "") {
-                    return true;
-                }
-            }
-            return false;
-        },
-
         getFilter: function () {
-            var retVal = domForm.toObject(this.id + "FilterForm");
+            var retVal = this.filter.toObject();
             lang.mixin(retVal, {
                 StartDate: this.getISOString("FromDate", "FromTime"),
                 EndDate: this.getISOString("ToDate", "ToTime")
@@ -249,10 +221,6 @@ define([
             return retVal;
         },
 
-        applyFilter: function () {
-            this.refreshGrid();
-        },
-
         //  Implementation  ---
         init: function (params) {
             if (this.inherited(arguments))
@@ -319,23 +287,24 @@ define([
                 var pSubMenu = new Menu();
                 this.menuFilterOwner = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "Owner").set("value", context.menuFilterOwner.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "Owner", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterCluster = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "ClusterTargetSelect").set("value", context.menuFilterCluster.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "ClusterTargetSelect", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 pSubMenu.addChild(new MenuSeparator());
                 this.menuFilterClearFilter = this.addMenuItem(pSubMenu, {
                     label: "Clear",
                     onClick: function () {
-                        context._onFilterClear();
+                        context.filter.clear();
+                        context.refreshGrid();
                     }
                 });
 
@@ -470,13 +439,6 @@ define([
                     }, sourceDiv);
                 });
             }
-
-            this.refreshFilterState();
-        },
-
-        refreshFilterState: function () {
-            var hasFilter = this.hasFilter();
-            dom.byId(this.id + "IconFilter").src = hasFilter ? "img/filter.png" : "img/noFilter.png";
         },
 
         ensureDFUWUPane: function (id, params) {

+ 109 - 0
esp/files/scripts/FilterDropDownWidget.js

@@ -0,0 +1,109 @@
+/*##############################################################################
+#   HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/dom",
+    "dojo/dom-form",
+    "dojo/on",
+
+    "dijit/registry",
+
+    "hpcc/_Widget",
+
+    "dojo/text!../templates/FilterDropDownWidget.html",
+
+    "dijit/form/DropDownButton",
+    "dijit/TooltipDialog",
+    "dijit/form/Form",
+    "dijit/form/Button",
+
+    "dojox/layout/TableContainer"
+
+], function (declare, arrayUtil, dom, domForm, on,
+                registry,
+                _Widget,
+                template) {
+    return declare("FilterDropDownWidget", [_Widget], {
+        templateString: template,
+        baseClass: "FilterDropDownWidget",
+
+        iconFilter: null,
+        filterDropDown: null,
+        filterForm: null,
+
+        postCreate: function (args) {
+            this.inherited(arguments);
+            this.filterDropDown = registry.byId(this.id + "FilterDropDown");
+            this.filterForm = registry.byId(this.id + "FilterForm");
+        },
+
+        startup: function (args) {
+            this.inherited(arguments);
+            this.iconFilter = dom.byId(this.id + "IconFilter");
+        },
+
+        //  Hitched actions  ---
+        _onFilterClear: function (event) {
+            this.clear();
+            this.filterDropDown.closeDropDown();
+            this.emit("clear");
+            this.refreshState();
+        },
+
+        _onFilterApply: function (event) {
+            this.filterDropDown.closeDropDown();
+            this.emit("apply");
+            this.refreshState();
+        },
+
+        //  Implementation  ---
+        clear: function () {
+            arrayUtil.forEach(this.filterForm.getDescendants(), function (item, idx) {
+                item.set("value", null);
+            });
+        },
+
+        setValue: function (id, value) {
+            registry.byId(id).set("value", value);
+            this.refreshState();
+        },
+
+        exists: function () {
+            var filter = this.toObject();
+            for (var key in filter) {
+                if (filter[key] != "") {
+                    return true;
+                }
+            }
+            return false;
+        },
+
+        toObject: function () {
+            var retVal = domForm.toObject(this.filterForm.id);
+            return retVal;
+        },
+
+        init: function (params) {
+            if (this.inherited(arguments))
+                return;
+        },
+
+        refreshState: function () {
+            this.iconFilter.src = this.exists() ? "img/filter.png" : "img/noFilter.png";
+        }
+    });
+});

+ 24 - 67
esp/files/scripts/GetDFUWorkunitsWidget.js

@@ -43,6 +43,7 @@ define([
     "hpcc/FileSpray",
     "hpcc/DFUWUDetailsWidget",
     "hpcc/TargetSelectWidget",
+    "hpcc/FilterDropDownWidget",
 
     "dojo/text!../templates/GetDFUWorkunitsWidget.html",
 
@@ -62,7 +63,7 @@ define([
 ], function (declare, arrayUtil,dom, domClass, domForm, date, on,
                 registry, Dialog, Menu, MenuItem, MenuSeparator, PopupMenuItem,
                 Grid, Keyboard, Selection, selector, ColumnResizer, DijitRegistry, Pagination,
-                _TabContainerWidget, ESPUtil, ESPDFUWorkunit, FileSpray, DFUWUDetailsWidget, TargetSelectWidget,
+                _TabContainerWidget, ESPUtil, ESPDFUWorkunit, FileSpray, DFUWUDetailsWidget, TargetSelectWidget, FilterDropDownWidget,
                 template) {
     return declare("GetDFUWorkunitsWidget", [_TabContainerWidget], {
         templateString: template,
@@ -70,19 +71,20 @@ define([
 
         workunitsTab: null,
         workunitsGrid: null,
+        filter: null,
         clusterTargetSelect: null,
         stateTargetSelect: null,
 
         postCreate: function (args) {
             this.inherited(arguments);
             this.workunitsTab = registry.byId(this.id + "_Workunits");
+            this.filter = registry.byId(this.id + "Filter");
             this.clusterTargetSelect = registry.byId(this.id + "ClusterTargetSelect");
             this.stateSelect = registry.byId(this.id + "StateSelect");
         },
 
         startup: function (args) {
             this.inherited(arguments);
-            this.initFilter();
         },
 
         getTitle: function () {
@@ -133,20 +135,6 @@ define([
             FileSpray.DFUWorkunitsAction(this.workunitsGrid.getSelected(), "Unprotect");
         },
 
-        _onFilterApply: function (event) {
-            registry.byId(this.id + "FilterDropDown").closeDropDown();
-            if (this.hasFilter()) {
-                this.applyFilter();
-            } else {
-                this.validateDialog.show();
-            }
-        },
-
-        _onFilterClear: function(event) {
-            this.clearFilter();
-            this.applyFilter();
-        },
-
         _onRowDblClick: function (id) {
             var wuTab = this.ensurePane(this.id + "_" + id, {
                 Wuid: id
@@ -185,31 +173,6 @@ define([
             }
         },
 
-        clearFilter: function () {
-            arrayUtil.forEach(registry.byId(this.id + "FilterForm").getDescendants(), function (item, idx) {
-                item.set('value', null);
-            });
-        },
-
-        hasFilter: function () {
-            var filter = domForm.toObject(this.id + "FilterForm");
-            for (var key in filter) {
-                if (filter[key] != "") {
-                    return true;
-                }
-            }
-            return false;
-        },
-
-        getFilter: function () {
-            var retVal = domForm.toObject(this.id + "FilterForm");
-            return retVal;
-        },
-
-        applyFilter: function () {
-            this.refreshGrid();
-        },
-
         //  Implementation  ---
         init: function (params) {
             if (this.inherited(arguments))
@@ -229,6 +192,14 @@ define([
                 includeBlank: true
             });
             this.selectChild(this.workunitsTab, true);
+
+            var context = this;
+            this.filter.on("clear", function (evt) {
+                context.refreshGrid();
+            });
+            this.filter.on("apply", function (evt) {
+                context.refreshGrid();
+            });
         },
 
         initTab: function () {
@@ -292,30 +263,31 @@ define([
                 });*/
                 this.menuFilterJobname = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "Jobname").set("value", context.menuFilterJobname.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "Jobname", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterCluster = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "ClusterTargetSelect").set("value", context.menuFilterCluster.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "ClusterTargetSelect", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterState = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "StateSelect").set("value", context.menuFilterState.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "StateSelect", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 pSubMenu.addChild(new MenuSeparator());
                 this.menuFilterClearFilter = this.addMenuItem(pSubMenu, {
                     label: "Clear",
                     onClick: function () {
-                        context._onFilterClear();
+                        context.filter.clear();
+                        context.refreshGrid();
                     }
                 });
 
@@ -414,15 +386,8 @@ define([
             this.workunitsGrid.startup();
         },
 
-        initFilter: function () {
-            this.validateDialog = new Dialog({
-                title: "Filter",
-                content: "No filter criteria specified."
-            });
-        },
-
         refreshGrid: function (args) {
-            this.workunitsGrid.set("query", this.getFilter());
+            this.workunitsGrid.set("query", this.filter.toObject());
         },
 
         refreshActionState: function () {
@@ -432,7 +397,6 @@ define([
             var hasNotProtected = false;
             var hasFailed = false;
             var hasNotFailed = false;
-            var hasFilter = this.hasFilter();
             for (var i = 0; i < selection.length; ++i) {
                 hasSelection = true;
                 if (selection[i] && selection[i].isProtected && selection[i].isProtected != "0") {
@@ -452,13 +416,6 @@ define([
             registry.byId(this.id + "SetToFailed").set("disabled", !hasNotProtected);
             registry.byId(this.id + "Protect").set("disabled", !hasNotProtected);
             registry.byId(this.id + "Unprotect").set("disabled", !hasProtected);
-
-            this.refreshFilterState();
-        },
-
-        refreshFilterState: function () {
-            var hasFilter = this.hasFilter();
-            dom.byId(this.id + "IconFilter").src = hasFilter ? "img/filter.png" : "img/noFilter.png";
         },
 
         ensurePane: function (id, params) {

+ 28 - 68
esp/files/scripts/WUQueryWidget.js

@@ -24,7 +24,6 @@ define([
     "dojo/on",
 
     "dijit/registry",
-    "dijit/Dialog",
     "dijit/Menu",
     "dijit/MenuItem",
     "dijit/MenuSeparator",
@@ -44,6 +43,7 @@ define([
     "hpcc/ESPWorkunit",
     "hpcc/WUDetailsWidget",
     "hpcc/TargetSelectWidget",
+    "hpcc/FilterDropDownWidget",
 
     "dojo/text!../templates/WUQueryWidget.html",
 
@@ -62,9 +62,9 @@ define([
     "dojox/layout/TableContainer"
 
 ], function (declare, lang, arrayUtil, dom, domClass, domForm, date, on,
-                registry, Dialog, Menu, MenuItem, MenuSeparator, PopupMenuItem,
+                registry, Menu, MenuItem, MenuSeparator, PopupMenuItem,
                 Grid, Keyboard, Selection, selector, ColumnResizer, DijitRegistry, Pagination,
-                _TabContainerWidget, WsWorkunits, ESPUtil, ESPWorkunit, WUDetailsWidget, TargetSelectWidget,
+                _TabContainerWidget, WsWorkunits, ESPUtil, ESPWorkunit, WUDetailsWidget, TargetSelectWidget, FilterDropDownWidget,
                 template) {
     return declare("WUQueryWidget", [_TabContainerWidget, ESPUtil.FormHelper], {
         templateString: template,
@@ -72,14 +72,14 @@ define([
 
         workunitsTab: null,
         workunitsGrid: null,
+        filter: null,
         clusterTargetSelect: null,
         stateSelect: null,
 
-        validateDialog: null,
-
         postCreate: function (args) {
             this.inherited(arguments);
             this.workunitsTab = registry.byId(this.id + "_Workunits");
+            this.filter = registry.byId(this.id + "Filter");
             this.clusterTargetSelect = registry.byId(this.id + "ClusterTargetSelect");
             this.stateSelect = registry.byId(this.id + "StateSelect");
             this.logicalFileSearchTypeSelect = registry.byId(this.id + "LogicalFileSearchType");
@@ -88,7 +88,6 @@ define([
         startup: function (args) {
             this.inherited(arguments);
             this.initContextMenu();
-            this.initFilter();
         },
 
         getTitle: function () {
@@ -150,20 +149,6 @@ define([
         _onDeschedule: function (event) {
         },
 
-        _onFilterApply: function (event) {
-            registry.byId(this.id + "FilterDropDown").closeDropDown();
-            if (this.hasFilter()) {
-                this.applyFilter();
-            } else {
-                this.validateDialog.show();
-            }
-        },
-
-        _onFilterClear: function (event) {
-            this.clearFilter();
-            this.applyFilter();
-        },
-
         _onRowDblClick: function (wuid) {
             var wuTab = this.ensurePane(this.id + "_" + wuid, {
                 Wuid: wuid
@@ -207,24 +192,8 @@ define([
         },
 
         //  Implementation  ---
-        clearFilter: function () {
-            arrayUtil.forEach(registry.byId(this.id + "FilterForm").getDescendants(), function (item, idx) {
-                item.set("value", null);
-            });
-        },
-
-        hasFilter: function () {
-            var filter = domForm.toObject(this.id + "FilterForm");
-            for (var key in filter) {
-                if (filter[key] != "") {
-                    return true;
-                }
-            }
-            return false;
-        },
-
         getFilter: function () {
-            var retVal = domForm.toObject(this.id + "FilterForm");
+            var retVal = this.filter.toObject();
             lang.mixin(retVal, {
                 StartDate: this.getISOString("FromDate", "FromTime"),
                 EndDate: this.getISOString("ToDate", "ToTime")
@@ -240,10 +209,6 @@ define([
             return retVal;
         },
 
-        applyFilter: function () {
-            this.refreshGrid();
-        },
-
         //  Implementation  ---
         init: function (params) {
             if (this.inherited(arguments))
@@ -267,6 +232,14 @@ define([
 
             this.initWorkunitsGrid();
             this.selectChild(this.workunitsTab, true);
+
+            var context = this;
+            this.filter.on("clear", function (evt) {
+                context.refreshGrid();
+            });
+            this.filter.on("apply", function (evt) {
+                context.refreshGrid();
+            });
         },
 
         initTab: function () {
@@ -327,37 +300,38 @@ define([
                 var pSubMenu = new Menu();
                 this.menuFilterOwner = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "Owner").set("value", context.menuFilterOwner.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "Owner", context.menuFilterOwner.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterJobname = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "Jobname").set("value", context.menuFilterJobname.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "Jobname", context.menuFilterJobname.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterCluster = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "ClusterTargetSelect").set("value", context.menuFilterCluster.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "ClusterTargetSelect", context.menuFilterCluster.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 this.menuFilterState = this.addMenuItem(pSubMenu, {
                     onClick: function (args) {
-                        context.clearFilter();
-                        registry.byId(context.id + "StateSelect").set("value", context.menuFilterState.get("hpcc_value"));
-                        context.applyFilter();
+                        context.filter.clear();
+                        context.filter.setValue(context.id + "StateSelect", context.menuFilterState.get("hpcc_value"));
+                        context.refreshGrid();
                     }
                 });
                 pSubMenu.addChild(new MenuSeparator());
                 this.menuFilterClearFilter = this.addMenuItem(pSubMenu, {
                     label: "Clear",
                     onClick: function () {
-                        context._onFilterClear();
+                        context.filter.clear();
+                        context.refreshGrid();
                     }
                 });
                 pMenu.addChild(new PopupMenuItem({
@@ -446,13 +420,6 @@ define([
             this.workunitsGrid.startup();
         },
 
-        initFilter: function () {
-            this.validateDialog = new Dialog({
-                title: "Filter",
-                content: "No filter criteria specified."
-            });
-        },
-
         refreshGrid: function (args) {
             this.workunitsGrid.set("query", this.getFilter());
         },
@@ -500,13 +467,6 @@ define([
 
             this.menuProtect.set("disabled", !hasNotProtected);
             this.menuUnprotect.set("disabled", !hasProtected);
-
-            this.refreshFilterState();
-        },
-
-        refreshFilterState: function () {
-            var hasFilter = this.hasFilter();
-            dom.byId(this.id + "IconFilter").src = hasFilter ? "img/filter.png" : "img/noFilter.png";
         },
 
         ensurePane: function (id, params) {

+ 12 - 22
esp/files/templates/DFUQueryWidget.html

@@ -43,28 +43,18 @@
                         </div>
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
-                    <img id="${id}IconFilter" src="img/noFilter.png" class="iconNoFilter"/>
-                    <div id="${id}FilterDropDown" data-dojo-type="dijit.form.DropDownButton">
-                        <span>Filter</span>
-                        <div data-dojo-type="dijit.TooltipDialog">
-                            <div id="${id}FilterForm" style="width:430px" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="dojox.layout.TableContainer">
-                                    <input id="${id}Name" title="Name:" name="LogicalName" colspan="2" style="width:100%;" data-dojo-props="trim: true, placeHolder:'*::somefile*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}Description" title="Description:" name="Description" colspan="2" style="width:100%;" data-dojo-props="trim: true, placeHolder:'Some*Description'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'JSmith*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}ClusterTargetSelect" title="Cluster:" name="ClusterName" colspan="2" style="display: inline-block; vertical-align: middle" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}FromSize" title="From&nbsp;Sizes:" name="FileSizeFrom" colspan="2" data-dojo-props="trim: true, placeHolder:'4096'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}ToSize" title="To&nbsp;Sizes:" name="FileSizeTo" colspan="2" data-dojo-props="trim: true, placeHolder:'16777216'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}FromDate" title="From&nbsp;Date:" name="StartDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
-                                    <input id="${id}FromTime" title="" name="FromTime" data-dojo-props="trim: true, placeHolder:'7:30 AM'" data-dojo-type="dijit.form.TimeTextBox" />
-                                    <input id="${id}ToDate" title="To&nbsp;Date:" name="EndDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
-                                    <input id="${id}ToTime" title="" name="ToTime" data-dojo-props="trim: true, placeHolder:'7:30 PM'" data-dojo-type="dijit.form.TimeTextBox" />
-                                    <input id="${id}LastNDays" title="Last&nbsp;N&nbsp;Days:" name="FirstN" colspan="2" data-dojo-props="dtrim: true, placeHolder:'2'" data-dojo-type="dijit.form.TextBox" />
-                                </div>
-                                <button data-dojo-attach-event="onClick:_onFilterApply" data-dojo-type="dijit.form.Button">Apply</button>
-                                <button data-dojo-attach-event="onClick:_onFilterClear" data-dojo-type="dijit.form.Button">Clear</button>
-                            </div>
-                        </div>
+                    <div id="${id}Filter" data-dojo-type="FilterDropDownWidget">
+                        <input id="${id}Name" title="Name:" name="LogicalName" colspan="2" style="width:100%;" data-dojo-props="trim: true, placeHolder:'*::somefile*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}Description" title="Description:" name="Description" colspan="2" style="width:100%;" data-dojo-props="trim: true, placeHolder:'Some*Description'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'JSmith*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}ClusterTargetSelect" title="Cluster:" name="ClusterName" colspan="2" style="display: inline-block; vertical-align: middle" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}FromSize" title="From&nbsp;Sizes:" name="FileSizeFrom" colspan="2" data-dojo-props="trim: true, placeHolder:'4096'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}ToSize" title="To&nbsp;Sizes:" name="FileSizeTo" colspan="2" data-dojo-props="trim: true, placeHolder:'16777216'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}FromDate" title="From&nbsp;Date:" name="StartDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
+                        <input id="${id}FromTime" title="" name="FromTime" data-dojo-props="trim: true, placeHolder:'7:30 AM'" data-dojo-type="dijit.form.TimeTextBox" />
+                        <input id="${id}ToDate" title="To&nbsp;Date:" name="EndDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
+                        <input id="${id}ToTime" title="" name="ToTime" data-dojo-props="trim: true, placeHolder:'7:30 PM'" data-dojo-type="dijit.form.TimeTextBox" />
+                        <input id="${id}LastNDays" title="Last&nbsp;N&nbsp;Days:" name="FirstN" colspan="2" data-dojo-props="dtrim: true, placeHolder:'2'" data-dojo-type="dijit.form.TextBox" />
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <div id="${id}NewPage" class="right" data-dojo-attach-event="onClick:_onNewPage" data-dojo-props="iconClass:'iconNewPage', showLabel:false" data-dojo-type="dijit.form.Button">Open in New Page</div>

+ 17 - 0
esp/files/templates/FilterDropDownWidget.html

@@ -0,0 +1,17 @@
+<span class="${baseClass}">
+    <img id="${id}IconFilter" src="img/noFilter.png" class="iconNoFilter"/>
+    <div id="${id}FilterDropDown" data-dojo-type="dijit.form.DropDownButton">
+        <span>Filter</span>
+        <div class="toolTip" data-dojo-type="dijit.TooltipDialog">
+            <div id="${id}FilterForm" style="width:460px" data-dojo-type="dijit.form.Form">
+                <div class="dijitDialogPaneContentArea" data-dojo-props="cols:2" data-dojo-type="dojox.layout.TableContainer">
+                    <span data-dojo-attach-point="containerNode"></span>
+                </div>
+                <div class="dijitDialogPaneActionBar">
+                    <button id="${id}FilterApply" data-dojo-attach-event="onClick:_onFilterApply" data-dojo-type="dijit.form.Button">Apply</button>
+                    <button id="${id}FilterClear" data-dojo-attach-event="onClick:_onFilterClear" data-dojo-type="dijit.form.Button">Clear</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</span>

+ 6 - 16
esp/files/templates/GetDFUWorkunitsWidget.html

@@ -12,22 +12,12 @@
                     <div id="${id}Protect" data-dojo-attach-event="onClick:_onProtect" data-dojo-type="dijit.form.Button">Protect</div>
                     <div id="${id}Unprotect" data-dojo-attach-event="onClick:_onUnprotect" data-dojo-type="dijit.form.Button">Unprotect</div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
-                    <img id="${id}IconFilter" src="img/noFilter.png" class="iconNoFilter"/>
-                    <div id="${id}FilterDropDown" data-dojo-type="dijit.form.DropDownButton">
-                        <span>Filter</span>
-                        <div data-dojo-type="dijit.TooltipDialog">
-                            <div id="${id}FilterForm" style="width:360px" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="dojox.layout.TableContainer">
-                                    <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'jsmi*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}Jobname" title="Jobname:" name="Jobname" colspan="2" data-dojo-props="trim: true, placeHolder:'log_analysis_1*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}ClusterTargetSelect" title="Cluster:" name="Cluster" colspan="2" data-dojo-props="trim: true, placeHolder:'r?x*'" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}StateSelect" title="State" name="StateReq" colspan="2" data-dojo-props="trim: true, placeHolder:'Created'" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}Type" title="Archived&nbsp;Only" name="Type" value="archived workunits" colspan="2" data-dojo-type="dijit.form.CheckBox" />
-                                </div>
-                                <button data-dojo-attach-event="onClick:_onFilterApply" data-dojo-type="dijit.form.Button">Apply</button>
-                                <button data-dojo-attach-event="onClick:_onFilterClear" data-dojo-type="dijit.form.Button">Clear</button>
-                            </div>
-                        </div>
+                    <div id="${id}Filter" data-dojo-type="FilterDropDownWidget">
+                        <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'jsmi*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}Jobname" title="Jobname:" name="Jobname" colspan="2" data-dojo-props="trim: true, placeHolder:'log_analysis_1*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}ClusterTargetSelect" title="Cluster:" name="Cluster" colspan="2" data-dojo-props="trim: true, placeHolder:'r?x*'" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}StateSelect" title="State" name="StateReq" colspan="2" data-dojo-props="trim: true, placeHolder:'Created'" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}Type" title="Archived&nbsp;Only" name="Type" value="archived workunits" colspan="2" data-dojo-type="dijit.form.CheckBox" />
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <div id="${id}NewPage" class="right" data-dojo-attach-event="onClick:_onNewPage" data-dojo-props="iconClass:'iconNewPage', showLabel:false" data-dojo-type="dijit.form.Button">Open in New Page</div>

+ 2 - 2
esp/files/templates/HPCCPlatformFilesWidget.html

@@ -4,12 +4,12 @@
             <div id="${id}StackController" style="width: 100%" data-dojo-props="containerId:'${id}TabContainer'" data-dojo-type="dijit.layout.StackController"></div>
         </div>
         <div id="${id}TabContainer" data-dojo-props="region: 'center', tabPosition: 'top'" style="width: 100%; height: 100%" data-dojo-type="dijit.layout.StackContainer">
-            <div id="${id}_Workunits" title="Workunits" data-dojo-type="GetDFUWorkunitsWidget">
-            </div>
             <div id="${id}_LogicalFiles" title="Logical Files" data-dojo-type="DFUQueryWidget">
             </div>
             <div id="${id}_LandingZones" title="Landing Zones" data-dojo-type="LZBrowseWidget">
             </div>
+            <div id="${id}_Workunits" title="Workunits" data-dojo-type="GetDFUWorkunitsWidget">
+            </div>
             <div id="${id}_XRef" title="XRef" data-dojo-type="dijit.layout.ContentPane">
             </div>
         </div>

+ 14 - 24
esp/files/templates/WUQueryWidget.html

@@ -17,30 +17,20 @@
                     <div id="${id}Reschedule" data-dojo-attach-event="onClick:_onReschedule" data-dojo-type="dijit.form.Button">Reschedule</div>
                     <div id="${id}Deschedule" data-dojo-attach-event="onClick:_onDeschedule" data-dojo-type="dijit.form.Button">Deschedule</div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
-                    <img id="${id}IconFilter" src="img/noFilter.png" class="iconNoFilter"/>
-                    <div id="${id}FilterDropDown" data-dojo-type="dijit.form.DropDownButton">
-                        <span>Filter</span>
-                        <div data-dojo-type="dijit.TooltipDialog" class="toolTip">
-                            <div id="${id}FilterForm" style="width:460px" data-dojo-type="dijit.form.Form">
-                                <div data-dojo-props="cols:2" data-dojo-type="dojox.layout.TableContainer">
-                                    <input id="${id}Wuid" title="WUID:" name="Wuid" colspan="2" data-dojo-props="trim: true, placeHolder:'W20130222-171723'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'jsmi*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}Jobname" title="Job&nbsp;Name:" name="Jobname" colspan="2" data-dojo-props="trim: true, placeHolder:'log_analysis_1*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}ClusterTargetSelect" title="Cluster:" name="Cluster" colspan="2" data-dojo-props="trim: true, placeHolder:'r?x*'" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}StateSelect" title="State:" name="State" colspan="2" data-dojo-props="trim: true, placeHolder:'Created'" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}ECL" title="ECL:" name="ECL" colspan="2" data-dojo-props="trim: true, placeHolder:':=dataset*'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}LogicalFile" title="Logical&nbsp;File" name="LogicalFile" colspan="2" data-dojo-props="trim: true, placeHolder:'*::somefile'" data-dojo-type="dijit.form.TextBox" />
-                                    <input id="${id}LogicalFileSearchType" title="Logical&nbsp;File&nbsp;Type" name="LogicalFileSearchType" colspan="2" data-dojo-type="TargetSelectWidget" />
-                                    <input id="${id}FromDate" title="From&nbsp;Date:" name="StartDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
-                                    <input id="${id}FromTime" name="FromTime" data-dojo-props="trim: true, placeHolder:'7:30 AM'" data-dojo-type="dijit.form.TimeTextBox" />
-                                    <input id="${id}ToDate" title="To&nbsp;Date:" name="EndDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
-                                    <input id="${id}ToTime" name="ToTime" data-dojo-props="trim: true, placeHolder:'7:30 PM'" data-dojo-type="dijit.form.TimeTextBox" />
-                                    <input id="${id}LastNDays" title="Last&nbsp;N&nbsp;Days:" name="LastNDays" data-dojo-props="dtrim: true, placeHolder:'2'" data-dojo-type="dijit.form.TextBox" />
-                                </div>
-                                <button id="${id}FilterApply" data-dojo-attach-event="onClick:_onFilterApply" data-dojo-type="dijit.form.Button">Apply</button>
-                                <button id="${id}FilterClear" data-dojo-attach-event="onClick:_onFilterClear" data-dojo-type="dijit.form.Button">Clear</button>
-                            </div>
-                        </div>
+                    <div id="${id}Filter" data-dojo-type="FilterDropDownWidget">
+                        <input id="${id}Wuid" title="WUID:" name="Wuid" colspan="2" style="width:100%" data-dojo-props="trim: true, placeHolder:'W20130222-171723'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}Owner" title="Owner:" name="Owner" colspan="2" data-dojo-props="trim: true, placeHolder:'jsmi*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}Jobname" title="Job&nbsp;Name:" name="Jobname" colspan="2" data-dojo-props="trim: true, placeHolder:'log_analysis_1*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}ClusterTargetSelect" title="Cluster:" name="Cluster" colspan="2" data-dojo-props="trim: true, placeHolder:'r?x*'" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}StateSelect" title="State:" name="State" colspan="2" data-dojo-props="trim: true, placeHolder:'Created'" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}ECL" title="ECL:" name="ECL" colspan="2" data-dojo-props="trim: true, placeHolder:':=dataset*'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}LogicalFile" title="Logical&nbsp;File" name="LogicalFile" colspan="2" data-dojo-props="trim: true, placeHolder:'*::somefile'" data-dojo-type="dijit.form.TextBox" />
+                        <input id="${id}LogicalFileSearchType" title="Logical&nbsp;File&nbsp;Type" name="LogicalFileSearchType" colspan="2" data-dojo-type="TargetSelectWidget" />
+                        <input id="${id}FromDate" title="From&nbsp;Date:" name="StartDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
+                        <input id="${id}FromTime" name="FromTime" data-dojo-props="trim: true, placeHolder:'7:30 AM'" data-dojo-type="dijit.form.TimeTextBox" />
+                        <input id="${id}ToDate" title="To&nbsp;Date:" name="EndDate" data-dojo-props="trim: true, placeHolder:'7/28/2013'" data-dojo-type="dijit.form.DateTextBox" />
+                        <input id="${id}ToTime" name="ToTime" data-dojo-props="trim: true, placeHolder:'7:30 PM'" data-dojo-type="dijit.form.TimeTextBox" />
+                        <input id="${id}LastNDays" title="Last&nbsp;N&nbsp;Days:" name="LastNDays" data-dojo-props="dtrim: true, placeHolder:'2'" data-dojo-type="dijit.form.TextBox" />
                     </div>
                     <span data-dojo-type="dijit.ToolbarSeparator"></span>
                     <div id="${id}NewPage" class="right" data-dojo-attach-event="onClick:_onNewPage" data-dojo-props="iconClass:'iconNewPage', showLabel:false" data-dojo-type="dijit.form.Button">Open in New Page</div>

+ 1 - 0
esp/services/ws_dfu/CMakeLists.txt

@@ -39,6 +39,7 @@ set (    SRCS
     )
 
 include_directories ( 
+         ${CMAKE_BINARY_DIR}/oss
          ./../../../dali/dfu 
          ./../../../system/mp 
          ./../../platform 

+ 1 - 0
esp/services/ws_workunits/CMakeLists.txt

@@ -44,6 +44,7 @@ set (    SRCS
     )
 
 include_directories (
+         ${CMAKE_BINARY_DIR}/oss
          ./../../esplib
          ./../../../system/mp
          ./../../platform

+ 12 - 3
plugins/unicodelib/unicodelib.cpp

@@ -744,6 +744,9 @@ UNICODELIB_API unsigned UNICODELIB_CALL ulUnicodeLocaleFind(unsigned srcLen, UCh
 
 UNICODELIB_API unsigned UNICODELIB_CALL ulUnicodeLocaleFindAtStrength(unsigned srcLen, UChar const * src, unsigned hitLen, UChar const * hit, unsigned instance, char const * localename, char strength)
 {
+    //Very strange behaviour - if source or pattern lengths are 0 search->getCollator() is invalid
+    if (srcLen == 0 || hitLen == 0)
+        return 0;
     UnicodeString const source(src, srcLen);
     UnicodeString const pattern(hit, hitLen);
     UErrorCode error = U_ZERO_ERROR;
@@ -976,9 +979,15 @@ UNICODELIB_API void UNICODELIB_CALL ulUnicodeLocaleFindReplace(unsigned & tgtLen
 UNICODELIB_API void UNICODELIB_CALL ulUnicodeLocaleFindAtStrengthReplace(unsigned & tgtLen, UChar * & tgt, unsigned srcLen, UChar const * src, unsigned stokLen, UChar const * stok, unsigned rtokLen, UChar const * rtok, char const * localename, char strength)
 {
     UnicodeString source(src, srcLen);
-    UnicodeString const pattern(stok, stokLen);
-    UnicodeString const replace(rtok, rtokLen);
-    doUnicodeLocaleFindAtStrengthReplace(source, pattern, replace, localename, strength);
+
+    //Very strange behaviour - if source or pattern lengths are 0 search->getCollator() is invalid
+    if (srcLen && stokLen)
+    {
+        UnicodeString const pattern(stok, stokLen);
+        UnicodeString const replace(rtok, rtokLen);
+        doUnicodeLocaleFindAtStrengthReplace(source, pattern, replace, localename, strength);
+    }
+
     tgtLen = source.length();
     tgt = (UChar *)CTXMALLOC(parentCtx, tgtLen*2);
     source.extract(0, tgtLen, tgt);

+ 20 - 0
testing/ecl/issue9879.ecl

@@ -0,0 +1,20 @@
+IMPORT Std;
+
+STD.Uni.LocaleFind(u'',u'abc',1,'');
+STD.Uni.LocaleFind(u'abc',u'',1,'');
+STD.Uni.LocaleFind(u'abc',u'b',1,'');
+
+STD.Uni.LocaleFindAtStrength(u'',u'abc',1,'',1);
+STD.Uni.LocaleFindAtStrength(u'abc',u'',1,'',1);
+STD.Uni.LocaleFindAtStrength(u'abc',u'b',1,'',1);
+
+STD.Uni.LocaleFindReplace(u'',u'abc',u'x','');
+STD.Uni.LocaleFindReplace(u'abc',u'',u'x','');
+STD.Uni.LocaleFindReplace(u'abc',u'B',u'x','');
+STD.Uni.LocaleFindReplace(u'abc',u'b',u'x','');
+
+STD.Uni.LocaleFindAtStrengthReplace(u'',u'abc',u'x','',1);
+STD.Uni.LocaleFindAtStrengthReplace(u'abc',u'',u'x','',1);
+STD.Uni.LocaleFindAtStrengthReplace(u'abc',u'B',u'x','',0);
+STD.Uni.LocaleFindAtStrengthReplace(u'abc',u'B',u'x','',1);
+STD.Uni.LocaleFindAtStrengthReplace(u'abc',u'B',u'x','',2);

+ 45 - 0
testing/ecl/key/issue9879.xml

@@ -0,0 +1,45 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>0</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>0</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>2</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>0</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>0</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>2</Result_6></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><Result_7></Result_7></Row>
+</Dataset>
+<Dataset name='Result 8'>
+ <Row><Result_8>abc</Result_8></Row>
+</Dataset>
+<Dataset name='Result 9'>
+ <Row><Result_9>abc</Result_9></Row>
+</Dataset>
+<Dataset name='Result 10'>
+ <Row><Result_10>axc</Result_10></Row>
+</Dataset>
+<Dataset name='Result 11'>
+ <Row><Result_11></Result_11></Row>
+</Dataset>
+<Dataset name='Result 12'>
+ <Row><Result_12>abc</Result_12></Row>
+</Dataset>
+<Dataset name='Result 13'>
+ <Row><Result_13>abc</Result_13></Row>
+</Dataset>
+<Dataset name='Result 14'>
+ <Row><Result_14>axc</Result_14></Row>
+</Dataset>
+<Dataset name='Result 15'>
+ <Row><Result_15>axc</Result_15></Row>
+</Dataset>

+ 11 - 1
thorlcr/activities/hashdistrib/thhashdistribslave.cpp

@@ -1091,11 +1091,21 @@ public:
     }
     bool sendRecv(ICommunicator &comm, CMessageBuffer &mb, rank_t r, mptag_t tag)
     {
+        mptag_t replyTag = createReplyTag();
         loop
         {
             if (aborted)
                 return false;
-            if (comm.sendRecv(mb, r, tag, MEDIUMTIMEOUT))
+            mb.setReplyTag(replyTag);
+            if (comm.send(mb, r, tag, MEDIUMTIMEOUT))
+                break;
+            // try again
+        }
+        loop
+        {
+            if (aborted)
+                return false;
+            if (comm.recv(mb, r, replyTag, NULL, MEDIUMTIMEOUT))
                 return true;
             // try again
         }