瀏覽代碼

Merge branch 'candidate-5.4.0'

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 10 年之前
父節點
當前提交
0781724f3c

+ 11 - 2
cmake_modules/FindBOOST_REGEX.cmake

@@ -43,7 +43,11 @@ IF (NOT BOOST_REGEX_FOUND)
         SET (boost_regex_lib "libboost_regex-mt.a")
       ENDIF()
     ELSEIF(WIN32)
-      SET (boost_regex_lib "libboost_regex-vc90-mt.lib") # note - this may not be the lib we need, but should be in same place as it...
+      IF (${ARCH64BIT} EQUAL 1)
+        SET (boost_regex_lib "boost_regex-vc100-mt.lib")
+      ELSE()
+        SET (boost_regex_lib "libboost_regex-vc90-mt.lib") # note - this may not be the lib we need, but should be in same place as it...
+      ENDIF()
     ENDIF()
     IF (NOT "${EXTERNALS_DIRECTORY}" STREQUAL "")
       IF (UNIX)
@@ -53,10 +57,15 @@ IF (NOT BOOST_REGEX_FOUND)
         SET (osdir "linux-i686-gcc4")
         ENDIF()
       ELSEIF(WIN32)
-        SET (osdir "windows-i386-vc90")
+    IF (${ARCH64BIT} EQUAL 1)
+      SET (osdir "windows-x86_64-vc100")
+    ELSE()
+      SET (osdir "windows-i386-vc90")
+    ENDIF()
       ELSE()
         SET (osdir "unknown")
       ENDIF()
+
       IF (NOT ("${osdir}" STREQUAL "unknown"))
         FIND_PATH (BOOST_REGEX_INCLUDE_DIR NAMES boost/regex.h PATHS "${EXTERNALS_DIRECTORY}/boost/include" NO_DEFAULT_PATH)
         FIND_LIBRARY (BOOST_REGEX_LIBRARIES NAMES ${boost_regex_lib} PATHS "${EXTERNALS_DIRECTORY}/boost/${osdir}/lib" NO_DEFAULT_PATH)

+ 5 - 1
cmake_modules/FindBZip2.cmake

@@ -25,7 +25,11 @@
 IF (NOT BZIP2_FOUND)
   IF (NOT "${EXTERNALS_DIRECTORY}" STREQUAL "")
     IF(WIN32)
-      SET (osdir "win32")
+      IF (${ARCH64BIT} EQUAL 1)
+        SET (osdir "win64")
+      ELSE()
+        SET (osdir "win32")
+      ENDIF()
       SET (bzip2ver "1.0.6")
       SET (bzip2_lib "libbz2")
     ENDIF()

+ 10 - 6
cmake_modules/FindICU.cmake

@@ -26,18 +26,22 @@ IF (NOT ICU_FOUND)
   IF (NOT "${EXTERNALS_DIRECTORY}" STREQUAL "")
     IF (UNIX)
       IF (${ARCH64BIT} EQUAL 1)
-        SET (osdir "linux64_gcc4.1.1")
+        SET (libdir "lib/linux64_gcc4.1.1")
       ELSE()
-        SET (osdir "linux32_gcc4.1.1")
+        SET (libdir "lib/linux32_gcc4.1.1")
       ENDIF()
     ELSEIF(WIN32)
-      SET (osdir )
+      IF (${ARCH64BIT} EQUAL 1)
+        SET (libdir "lib64")
+      ELSE()
+        SET (libdir "lib")
+      ENDIF()
     ELSE()
-      SET (osdir "unknown")
+      SET (libdir "unknown")
     ENDIF()
-    IF (NOT ("${osdir}" STREQUAL "unknown"))
+    IF (NOT ("${libdir}" STREQUAL "unknown"))
       FIND_PATH (ICU_INCLUDE_DIR NAMES unicode/uchar.h PATHS "${EXTERNALS_DIRECTORY}/icu/include" NO_DEFAULT_PATH)
-      FIND_LIBRARY (ICU_LIBRARIES NAMES icuuc PATHS "${EXTERNALS_DIRECTORY}/icu/lib/${osdir}" NO_DEFAULT_PATH)
+      FIND_LIBRARY (ICU_LIBRARIES NAMES icuuc PATHS "${EXTERNALS_DIRECTORY}/icu/${libdir}" NO_DEFAULT_PATH)
     ENDIF()
   ENDIF()
 

+ 5 - 1
cmake_modules/FindXERCES.cmake

@@ -24,7 +24,11 @@
 
 if (NOT XERCES_FOUND)
   IF (WIN32)
-    SET (xerces_libs "xerces-c_2")
+    IF (${ARCH64BIT} EQUAL 1)
+      SET (xerces_libs "xerces-c_3")
+    ELSE()
+      SET (xerces_libs "xerces-c_2")
+    ENDIF()
   ELSE()
     SET (xerces_libs "xerces-c")
   ENDIF()

+ 14 - 8
ecl/hqlcpp/hqlresource.cpp

@@ -5160,15 +5160,21 @@ IHqlExpression * EclResourcer::doCreateResourced(IHqlExpression * expr, Resource
         }
     case no_select:
         {
-            IHqlExpression * ds = expr->queryChild(0);
-            OwnedHqlExpr newDs = createResourced(ds, ownerGraph, expandInParent, false);
-            if (ds != newDs)
+            //If this isn't a new selector, then it must be <LEFT|RIGHT>.child-dataset, which will not be mapped
+            //and the dataset will not have been resourced
+            if (isNewSelector(expr))
             {
-                args.append(*LINK(newDs));
-                unwindChildren(args, expr, 1);
-                if (!expr->hasAttribute(newAtom) && isNewSelector(expr) && (newDs->getOperator() != no_select))
-                    args.append(*LINK(queryNewSelectAttrExpr()));
-                same = false;
+                IHqlExpression * ds = expr->queryChild(0);
+                OwnedHqlExpr newDs = createResourced(ds, ownerGraph, expandInParent, false);
+
+                if (ds != newDs)
+                {
+                    args.append(*LINK(newDs));
+                    unwindChildren(args, expr, 1);
+                    if (!expr->hasAttribute(newAtom) && (newDs->getOperator() != no_select))
+                        args.append(*LINK(queryNewSelectAttrExpr()));
+                    same = false;
+                }
             }
             break;
         }

+ 3 - 2
esp/platform/espcontext.cpp

@@ -767,9 +767,10 @@ void setCFD(const char* cfd)
     g_cfd.clear();
     if(cfd&&*cfd)
         g_cfd.append(cfd);
-
     g_cfd.trim();
-    if(g_cfd.length() > 0)
+    if (g_cfd.length())
+        makeAbsolutePath(g_cfd, true);
+    if (g_cfd.length())
     {
         char lastChar = g_cfd.charAt(g_cfd.length() - 1);
         if(lastChar != PATHSEPCHAR && lastChar != '/')

+ 1 - 1
esp/platform/espp.cpp

@@ -331,8 +331,8 @@ int init_main(int argc, char* argv[])
             procpt->getProp("@componentfilesDir", componentfilesDir);
         if(componentfilesDir.length() > 0 && strcmp(componentfilesDir.str(), ".") != 0)
         {
-            DBGLOG("componentfiles are under %s", componentfilesDir.str());
             setCFD(componentfilesDir.str());
+            DBGLOG("componentfiles are under %s", getCFD());
         }
 
         StringBuffer sehsetting;

+ 1 - 1
esp/src/Visualization

@@ -1 +1 @@
-Subproject commit da4fdbc3eedec680f916d71c3f2d414730185a8b
+Subproject commit a9ab832b5106f8b2b5073e8b0c9b6dcf5bd6cf38

+ 4 - 0
esp/src/eclwatch/ESPRequest.js

@@ -283,6 +283,10 @@ define([
                 }
             },
 
+            endsWith: function (str, suffix) {
+                return str.indexOf(suffix, str.length - suffix.length) !== -1;
+            },
+
             getIdentity: function (item) {
                 return item[this.idProperty];
             },

+ 1 - 1
esp/src/eclwatch/FileSpray.js

@@ -149,7 +149,7 @@ define([
                 displayName: row.Name,
                 type: "dropzone",
                 partialPath: "",
-                fullPath: row.Path + "/",
+                fullPath: row.Path + (row.Path && !this.endsWith(row.Path, "/") ? "/" : ""),
                 DropZone: row
             });
         },

+ 1 - 4
esp/src/eclwatch/LZBrowseWidget.js

@@ -295,13 +295,10 @@ define([
                 var context = this;
                 arrayUtil.forEach(selections, function (item, idx) {
                     var request = domForm.toObject(context.id + formID);
-                    if (request.namePrefix && !context.endsWith(request.namePrefix, "::")) {
-                        request.namePrefix += "::";
-                    }
                     lang.mixin(request, {
                         sourceIP: item.DropZone.NetAddress,
                         sourcePath: item.fullPath,
-                        destLogicalName: request.namePrefix + item.targetName
+                        destLogicalName: request.namePrefix + (request.namePrefix && !context.endsWith(request.namePrefix, "::") && item.targetName && !context.startsWith(item.targetName, "::") ? "::" : "") + item.targetName
                     });
                     doSpray(request, item);
                 });

+ 15 - 6
esp/src/eclwatch/WUStatsWidget.js

@@ -89,7 +89,7 @@ define([
                     this.targetID = targetID;
                     this.dimensionID = dimensionID;
                     this.dimension = crossfilter.dimension(function (d) { return d[dimensionID]; });
-                    this.group = this.dimension.group().reduceCount();
+                    this.group = this.dimension.group().reduceSum(function (d) { return d.RawValue; });
 
                     this.widget = new MultiChartSurface()
                         .target(targetID)
@@ -131,12 +131,16 @@ define([
 
                 context.stats = crossfilter([]);
                 context.summaryByKind = context.stats.dimension(function (d) { return d.Kind; });
-                context.groupByKind = context.summaryByKind.group().reduceSum(function (d) { return d.RawValue; });
+                context.groupByKind = context.summaryByKind.group().reduceCount();
 
                 context.select = registry.byId(context.id + "Kind");
                 var prevKind = "";
                 context.select.on("change", function (newValue) {
                     if (prevKind !== newValue) {
+                        context.pieCreatorType.resetFilter();
+                        context.pieScopeType.resetFilter();
+                        context.prevScope = null;
+                        context.summaryByScope.filterAll();
                         context.summaryByKind.filter(newValue);
                         context.doRender(context.select);
                         prevKind = newValue;
@@ -165,7 +169,7 @@ define([
                 ;
 
                 context.prevScope = null;
-                context.scopes.click = function (row, column) {
+                context.scopes.click = SunburstPartition.prototype.debounce(function (row, column) {
                     if (row.id === "") {
                         context.prevScope = null;
                         context.summaryByScope.filter(null);
@@ -179,7 +183,7 @@ define([
                         });
                     }
                     context.doRender(context.scopes);
-                };
+                }, 250);
 
                 context.bar = new MultiChartSurface()
                     .target(context.id + "Stats")
@@ -266,6 +270,12 @@ define([
                     .data(tree)
                 ;
                 this.scopesSurface
+                    .title("Scope" + (this.prevScope ? " (" + this.prevScope + ")" : ""))
+                    .render()
+                ;
+            } else {
+                this.scopesSurface._text
+                    .text("Scope" + (this.prevScope ? " (" + this.prevScope + ")" : ""))
                     .render()
                 ;
             }
@@ -289,7 +299,6 @@ define([
                     return [(this.prevScope && row.Scope.indexOf(this.prevScope) === 0 ? row.Scope.substring(this.prevScope.length + 1) : row.Scope), row.RawValue];
                 }, this);
             }
-            this.scopesSurface.title("Scope" + (this.prevScope ? " (" + this.prevScope + ")" : "")).render();
             var statsLabel = [this.select.get("value"), this.pieCreatorType.filter, this.pieScopeType.filter, this.prevScope].filter(function (item) {
                 return item;
             }).join(", ") || "Unknown";
@@ -326,7 +335,7 @@ define([
  
                     var kind = context.select.get("value");
                     context.select.set("options", context.groupByKind.all().map(function (row) {
-                        return { label: row.key, value: row.key, selected: kind === row.key };
+                        return { label: row.key + " (" + row.value + ")", value: row.key, selected: kind === row.key };
                     }));
 
                     if (kind) context.summaryByKind.filter(kind);

+ 24 - 8
lib2/CMakeLists.txt

@@ -26,16 +26,32 @@ if (APPLE)
 elseif (WIN32)
     #TODO:  Should find these dlls not assume them.
     if (NOT USE_NATIVE_LIBRARIES)
+      if (${ARCH64BIT} EQUAL 1)
+        find_file (BOOST_REGEX_BIN "boost_regex-vc100-mt-1_46.dll" "${EXTERNALS_DIRECTORY}/boost/windows-x86_64-vc100/lib" NO_DEFAULT_PATH)
+      else()
         find_file (BOOST_REGEX_BIN "boost_regex-vc90-mt-1_44.dll" "${EXTERNALS_DIRECTORY}/boost/windows-i386-vc90/lib" NO_DEFAULT_PATH)
+      endif()
     endif (NOT USE_NATIVE_LIBRARIES)
-    find_file (ICU_DT_BIN "icudt36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
-    find_file (ICU_IN_BIN "icuin36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
-    find_file (ICU_UC_BIN "icuuc36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
-    find_file (OPENSSL_LIB_BIN "libeay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win32/lib" NO_DEFAULT_PATH)
-    find_file (OPENSSL_SSL_BIN "ssleay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win32/lib" NO_DEFAULT_PATH)
-    find_file (XALAN_C_BIN "Xalan-C_1_10.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
-    find_file (XALAN_MESSAGES_BIN "XalanMessages_1_10.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
-    find_file (XERCES_C_BIN "xerces-c_2_7.dll" "${EXTERNALS_DIRECTORY}/xalan/xerces-c/bin" NO_DEFAULT_PATH)
+    if (${ARCH64BIT} EQUAL 1)
+      find_file (ICU_DT_BIN "icudt54.dll" "${EXTERNALS_DIRECTORY}/icu/bin64" NO_DEFAULT_PATH)
+      find_file (ICU_IN_BIN "icuin54.dll" "${EXTERNALS_DIRECTORY}/icu/bin64" NO_DEFAULT_PATH)
+      find_file (ICU_UC_BIN "icuuc54.dll" "${EXTERNALS_DIRECTORY}/icu/bin64" NO_DEFAULT_PATH)
+      find_file (OPENSSL_LIB_BIN "libeay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win64/lib" NO_DEFAULT_PATH)
+      find_file (OPENSSL_SSL_BIN "ssleay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win64/lib" NO_DEFAULT_PATH)
+      find_file (XALAN_C_BIN "Xalan-C_1_11.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
+      find_file (XALAN_MESSAGES_BIN "XalanMessages_1_11.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
+      find_file (XERCES_C_BIN "xerces-c_3_1.dll" "${EXTERNALS_DIRECTORY}/xalan/xerces-c/bin" NO_DEFAULT_PATH)
+    else()
+      find_file (ICU_DT_BIN "icudt36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
+      find_file (ICU_IN_BIN "icuin36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
+      find_file (ICU_UC_BIN "icuuc36.dll" "${EXTERNALS_DIRECTORY}/icu/bin" NO_DEFAULT_PATH)
+      find_file (OPENSSL_LIB_BIN "libeay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win32/lib" NO_DEFAULT_PATH)
+      find_file (OPENSSL_SSL_BIN "ssleay32.dll" "${EXTERNALS_DIRECTORY}/openssl/win32/lib" NO_DEFAULT_PATH)
+      find_file (XALAN_C_BIN "Xalan-C_1_10.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
+        find_file (XALAN_MESSAGES_BIN "XalanMessages_1_10.dll" "${EXTERNALS_DIRECTORY}/xalan/xalan-c/bin" NO_DEFAULT_PATH)
+        find_file (XERCES_C_BIN "xerces-c_2_7.dll" "${EXTERNALS_DIRECTORY}/xalan/xerces-c/bin" NO_DEFAULT_PATH)
+    endif()
+
     set(DYLIBS "")
     if (NOT USE_NATIVE_LIBRARIES)
         list(APPEND DYLIBS ${BOOST_REGEX_BIN})

+ 4 - 3
plugins/redis/README.md

@@ -93,7 +93,7 @@ The core points to note here are:
    127.0.0.1:6379 is used. *Note:* 6379 is the default port for **redis-server**.
    * `UNSIGNED timeout` has units *ms* and has a default value of 1 second. This is not a timeout duration for an entire plugin call but rather that set for each
    communication transaction with the redis server. *c.f.* 'Behaviour and Implementation Details' below.
-   * `UNSIGNED expire` has a default of **0**, i.e. *forever*.
+   * `UNSIGNED expire` has units seconds and a default of **0**, i.e. *forever*.
 
 ###The redisServer MODULE
 To avoid the cumbersome and unnecessary need to constantly pass `options` and `password` with each function call, the module `redisServer` can be imported to effectively 
@@ -156,7 +156,7 @@ Behaviour and Implementation Details
 ------------------------------------
 A few notes to point out here:
    * PUB-SUB channels are not disconnected from the keyspace as they are in their native redis usage. The key itself is used as the lock with its value being set as the channel to later
-   PUBLISH on or SUBSCRIBE to. This channel is unique to the *server-IP*, *cache-port*, *key*, and *database*. It is in fact the underscore concatenation of all four, prefixed with the string **redis_ecl_lock**.
+   PUBLISH on or SUBSCRIBE to. This channel is a string, unique by only the *key* and *database*, prefixed with **'redis_ecl_lock'**.
    * The lock itself is set to expire with a duration equal to the `timeout` value passed to the `locking.Exists(<key>` function (default 1s).
    * It is possible to manually 'unlock' this lock (`DEL` the key) via the `locking.Unlock(<key>)` function. *Note:* this function will fail on any communication or reply error however, 
    it will **silently fail**, leaving the lock to expire, if the server observes any change to the key during the function call duration.
@@ -184,5 +184,6 @@ A few notes to point out here:
 | Expire              | 1       | 5       | new connection   |
 | GetOrLock           | 7       | 11      | new connection   |
 | GetOrLock (locked)  | 8       | 12      | new connection   |
-| SetAndPublish       | 2       | 6       | new connection   |
+| SetAndPublish (value length > 29) | 1       | 5       | new connection   |
+| SetAndPublish (value length < 29) | 4       | 8       | new connection   |
 | Unlock              | 5       | 9       | new connection   |

+ 98 - 37
plugins/redis/redis.cpp

@@ -135,6 +135,7 @@ protected :
     void handleLockOnSet(ICodeContext * ctx, const char * key, const char * value, size_t size, unsigned expire);
     void handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAttr * retVal, const char * password);
     void encodeChannel(StringBuffer & channel, const char * key) const;
+    bool noScript(const redisReply * reply) const;
     bool lock(ICodeContext * ctx, const char * key, const char * channel);
     //--------------------------------------------------------------------------------------
 
@@ -192,8 +193,8 @@ void Connection::connect(ICodeContext * ctx, unsigned __int64 _database, const c
 {
     struct timeval to = { timeout/1000, (timeout%1000)*1000 };
     context = redisConnectWithTimeout(ip.str(), port, to);
-    redisSetTimeout(context, to);
     assertConnection();
+    redisSetTimeout(context, to);
 
     //The following is the dissemination of the two methods authenticate(ctx, password) & selectDB(ctx, _database)
     //such that they may be pipelined to save an extra round trip to the server and back.
@@ -402,7 +403,7 @@ void Connection::assertOnCommandError(const redisReply * reply, const char * cmd
 }
 void Connection::assertAuthorization(const redisReply * reply)
 {
-    if (strncmp(reply->str, "NOAUTH", 6) == 0)
+    if (reply && reply->str && ( strncmp(reply->str, "NOAUTH", 6) == 0 || strncmp(reply->str, "ERR operation not permitted", 27) == 0 ))
     {
         VStringBuffer msg("Redis Plugin: server authentication failed - %s", reply->str);
         rtlFail(0, msg.str());
@@ -719,6 +720,8 @@ void Connection::unlock(ICodeContext * ctx, const char * key)
 }
 void Connection::handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAttr * retVal, const char * password)
 {
+    //NOTE: This routine can only return an empty string under one condition, that which indicates to the caller that the key was successfully locked.
+
     StringBuffer channel;
     encodeChannel(channel, key);
 
@@ -726,10 +729,17 @@ void Connection::handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAtt
     if (lock(ctx, key, channel.str()))
         return;
 
+#if(0)//Test empty string handling by deleting the lock/value, and thus GET returns REDIS_REPLY_NIL as the reply type and an empty string.
+    {
+    OwnedReply pubReply = Reply::createReply(redisCommand(context, "DEL %b", key, strlen(key)));
+    assertOnError(pubReply->query(), "del fail");
+    }
+#endif
+
     //SUB before GET
     //Requires separate connection from GET so that the replies are not mangled. This could be averted
     Owned<Connection> subConnection = new Connection(ctx, options.str(), ip.str(), port, serverIpPortPasswordHash, database, password, timeout);
-    OwnedReply reply = Reply::createReply(redisCommand(subConnection->context, "SUBSCRIBE %b", channel.str(), channel.length()));
+    OwnedReply reply = Reply::createReply(redisCommand(subConnection->context, "SUBSCRIBE %b", channel.str(), (size_t)channel.length()));
     assertOnCommandErrorWithKey(reply->query(), "GET", key);
     if (reply->query()->type == REDIS_REPLY_ARRAY && strcmp("subscribe", reply->query()->element[0]->str) != 0 )
     {
@@ -737,9 +747,9 @@ void Connection::handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAtt
         rtlFail(0, msg.str());
     }
 
-#if(0)
+#if(0)//Test publish before GET.
     {
-    OwnedReply pubReply = Reply::createReply(redisCommand(context, "PUBLISH %b %b", channel.str(), channel.length(), "foo", 3));
+    OwnedReply pubReply = Reply::createReply(redisCommand(context, "PUBLISH %b %b", channel.str(), (size_t)channel.length(), "foo", (size_t)3));
     assertOnError(pubReply->query(), "pub fail");
     }
 #endif
@@ -747,34 +757,32 @@ void Connection::handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAtt
     //Now GET
     reply->setClear((redisReply*)redisCommand(context, "GET %b", key, strlen(key)));
     assertOnCommandErrorWithKey(reply->query(), "GET", key);
-    assertKey(reply->query(), key);
 
-#if(0)
+#if(0)//Test publish after GET.
     {
-    OwnedReply pubReply = Reply::createReply(redisCommand(context, "PUBLISH %b %b", channel.str(), channel.length(), "foo", 3));
+    OwnedReply pubReply = Reply::createReply(redisCommand(context, "PUBLISH %b %b", channel.str(), (size_t)channel.length(), "foo", (size_t)3));
     assertOnError(pubReply->query(), "pub fail");
     }
 #endif
 
-    //Check if returned value is locked
-    if (strncmp(reply->query()->str, REDIS_LOCK_PREFIX, strlen(REDIS_LOCK_PREFIX)) != 0)
+    //Only return an actual value, i.e. neither the lock value nor an empty string. The latter is unlikely since we know that lock()
+    //failed, indicating that the key existed. If this is an actual value, it is however, possible for it to have been DELeted in the interim.
+    if (reply->query()->type != REDIS_REPLY_NIL && reply->query()->str && strncmp(reply->query()->str, REDIS_LOCK_PREFIX, strlen(REDIS_LOCK_PREFIX)) != 0)
     {
-        //Not locked so return value
         retVal->set(reply->query()->len, reply->query()->str);
         return;
     }
     else
     {
-        //Check that we SUBSCRIBEd to the correct channel (which could have been manually SET).
-        if (strcmp(reply->query()->str, channel.str()) !=0 )
+        //Check that the lock was set by this plugin and thus that we subscribed to the expected channel.
+        if (reply->query()->str && strcmp(reply->query()->str, channel.str()) !=0 )
         {
             VStringBuffer msg("Redis Plugin: ERROR - the key '%s', on database %" I64F "u, is locked with a channel ('%s') different to that subscribed to (%s).", key, database, reply->query()->str, channel.str());
             rtlFail(0, msg.str());
-            //MORE: We could attempt to recover at this stage by subscribing to the channel that the key was actually locked with.
-            //However, we may have missed the massage publication already or by then.
-            //If we ever changed the semantics of the 'timeout' to be that of these plugin functions rather than each redis call, we might as well
-            //subscribe again if there was time left on the clock.
-            //Since they are not, we could, though is this desirable behaviour?
+            //MORE: In theory, it is possible to recover at this stage by subscribing to the channel that the key was actually locked with.
+            //However, we may have missed the massage publication already or by then, but could SUB again in case we haven't.
+            //More importantly and furthermore, the publication (in SetAndPublish<type>) will only publish to the channel encoded by
+            //this plugin, rather than the string retrieved as the lock value (the value of the locked key).
         }
 #if(0)//Added to allow for manual pub testing via redis-cli
         struct timeval to = { 10, 0 };//10secs
@@ -789,37 +797,90 @@ void Connection::handleLockOnGet(ICodeContext * ctx, const char * key, MemoryAtt
         assertOnCommandErrorWithKey(nakedReply, "GET", key);
         if (nakedReply->type == REDIS_REPLY_ARRAY && strcmp("message", nakedReply->element[0]->str) == 0)
         {
-            retVal->set(nakedReply->element[2]->len, nakedReply->element[2]->str);//return the published value rather than another (WATCHed) GET.
-            return;
+            //We are about to return a value, to conform with other Get<type> functions, fail if the key did not exist.
+            //Since the value is sent via a published message, there is no direct reply struct so assume that an empty
+            //string is equivalent to a non-existent key.
+            //More importantly, it is paramount that this routine only return an empty string under one condition, that
+            //which indicates to the caller that the key was successfully locked.
+            //NOTE: it is possible for an empty message to have been PUBLISHed.
+            if (nakedReply->element[2]->len > 0)
+            {
+                retVal->set(nakedReply->element[2]->len, nakedReply->element[2]->str);//return the published value rather than another (WATCHed) GET.
+                return;
+            }
+            VStringBuffer msg("Redis Plugin: ERROR - the requested key '%s' does not exist on database %" I64F "u", key, database);
+            rtlFail(0, msg.str());
         }
     }
     throwUnexpected();
 }
 void Connection::handleLockOnSet(ICodeContext * ctx, const char * key, const char * value, size_t size, unsigned expire)
 {
-    StringBuffer cmd("SET %b %b");
-    RedisPlugin::appendExpire(cmd, expire);
-
     //Due to locking logic surfacing into ECL, any locking.set (such as this is) assumes that they own the lock and therefore go ahead and set regardless.
-    //It is possible for a process/call to 'own' a lock and store this info in the LockObject, however, this prevents sharing between clients.
-    redisAppendCommand(context, cmd.str(), key, strlen(key), value, size);//SET
     StringBuffer channel;
     encodeChannel(channel, key);
-    redisAppendCommand(context, "PUBLISH %b %b", channel.str(), channel.length(), value, size);//PUB
 
-    //Now read and assert replies
-    OwnedReply replyContainer = new Reply();
-    readReplyAndAssertWithKey(replyContainer, "SET", key);//SET reply
-    readReplyAndAssertWithKey(replyContainer, "PUB for the key", key);//PUB reply
+    if (size > 29)//c.f. 1st note below.
+    {
+        OwnedReply replyContainer = new Reply();
+        if (expire == 0)
+        {
+            const char * luaScriptSHA1 = "2a4a976d9bbd806756b2c7fc1e2bc2cb905e68c3"; //NOTE: update this if luaScript is updated!
+            replyContainer->setClear((redisReply*)redisCommand(context, "EVALSHA %b %d %b %b %b", luaScriptSHA1, (size_t)40, 1, key, strlen(key), channel.str(), (size_t)channel.length(), value, size));
+            if (noScript(replyContainer->query()))
+            {
+                const char * luaScript = "redis.call('SET', KEYS[1], ARGV[2]) redis.call('PUBLISH', ARGV[1], ARGV[2]) return";
+                replyContainer->setClear((redisReply*)redisCommand(context, "EVAL %b %d %b %b %b", luaScript, strlen(luaScript), 1, key, strlen(key), channel.str(), (size_t)channel.length(), value, size));
+            }
+        }
+        else
+        {
+            const char * luaScriptWithExpireSHA1 = "c68d1706d7dc6342d5fc1d651e238931bd75320d"; //NOTE: update this if luaScriptWithExpire is updated!
+            replyContainer->setClear((redisReply*)redisCommand(context, "EVALSHA %b %d %b %b %b %d", luaScriptWithExpireSHA1, (size_t)40, 1, key, strlen(key), channel.str(), (size_t)channel.length(), value, size, expire/1000));
+            if (noScript(replyContainer->query()))
+            {
+                const char * luaScriptWithExpire = "redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3]) redis.call('PUBLISH', ARGV[1], ARGV[2]) return";
+                replyContainer->setClear((redisReply*)redisCommand(context, "EVAL %b %d %b %b %b %d", luaScriptWithExpire, strlen(luaScriptWithExpire), 1, key, strlen(key), channel.str(), (size_t)channel.length(), value, size, expire/1000));
+            }
+        }
+        assertOnCommandErrorWithKey(replyContainer->query(), "SET", key);
+    }
+    else
+    {
+        StringBuffer cmd("SET %b %b");
+        RedisPlugin::appendExpire(cmd, expire);
+        redisAppendCommand(context, "MULTI");
+        redisAppendCommand(context, cmd.str(), key, strlen(key), value, size);//SET
+        redisAppendCommand(context, "PUBLISH %b %b", channel.str(), (size_t)channel.length(), value, size);//PUB
+        redisAppendCommand(context, "EXEC");
+
+        //Now read and assert replies
+        OwnedReply reply = new Reply();
+        readReplyAndAssertWithKey(reply, "SET", key);//MULTI reply
+        readReplyAndAssertWithKey(reply, "SET", key);//SET reply
+        readReplyAndAssertWithKey(reply, "PUB for the key", key);//PUB reply
+        readReplyAndAssertWithKey(reply, "SET", key);//EXEC reply
+    }
+
+    //NOTE: When setting and publishing the data with a pipelined MULTI-SET-PUB-EXEC, the data is sent twice, once with the SET and again with the PUBLISH.
+    //To prevent this, send the data to the server only once with a server-side lua script that then sets and publishes the data from the server.
+    //However, there is a transmission overhead for this method that may still be larger than sending the data twice if it is small enough.
+    //multi-set-pub-exec (via strings) has a transmission length of - "MULTI SET" + key + value + "PUBLISH" + channel + value  = 5 + 3 + key + 7 + value + channel + value + 4
+    //The lua script (assuming the script already exists on the server) a length of - "EVALSHA" + digest + "1" + key + channel + value = 7 + 40 + 1 + key + channel + value
+    //Therefore, they have same length when: 19 + value = 48 => value = 29.
 
-    //NOTE: Pipelining the above commands may not be the desired behaviour but instead only PUBLISH upon a successful SET. Doing both regardless, does however ensure
-    //(assuming only the SET fails) that any subscribers do in fact get their requested key-value even if the SET fails. However, this may not be expected behaviour
-    //as it's now possible for the key-value to actually exists in the cache when it was retrieved via redis plugin get function. This is documented in the README.
+    //NOTE: Pipelining the above commands may not be the expected behaviour, instead only PUBLISH upon a successful SET. Doing both regardless, does however ensure
+    //(assuming only the SET fails) that any subscribers do in fact get their requested key-value even if the SET fails. This may not be expected behaviour
+    //as it is now possible for the key-value to NOT actually exist in the cache though it was retrieved via a redis plugin get function. This is documented in the README.
     //Further more, it is possible that the locked value and thus the channel stored within the key is not that expected, i.e. computed via encodeChannel() (e.g.
     //if set by a non-conforming external client/process). It is however, possible to account for this via using a GETSET instead of just the SET. This returns the old
     //value stored, this can then be checked if it is a lock (i.e. has at least the "redis_key_lock prefix"), if it doesn't, PUB on the channel from encodeChannel(),
     //otherwise PUB on the value retrieved from GETSET or possibly only if it at least has the prefix "redis_key_lock".
-    //This would however, prevent the two commands from being pipelined, as the GETSET would need to return before publishing.
+    //This would however, prevent the two commands from being pipelined, as the GETSET would need to return before publishing. It would also mean sending the data twice.
+}
+bool Connection::noScript(const redisReply * reply) const
+{
+    return (reply && reply->type == REDIS_REPLY_ERROR && strncmp(reply->str, "NOSCRIPT", 8) == 0);
 }
 //--------------------------------------------------------------------------------
 //                           ECL SERVICE ENTRYPOINTS
@@ -827,21 +888,21 @@ void Connection::handleLockOnSet(ICodeContext * ctx, const char * key, const cha
 //-----------------------------------SET------------------------------------------
 ECL_REDIS_API void ECL_REDIS_CALL SyncLockRSetStr(ICodeContext * ctx, size32_t & returnLength, char * & returnValue, const char * key, size32_t valueLength, const char * value, const char * options, unsigned __int64 database, unsigned expire, const char * password, unsigned __int64 timeout)
 {
-    SyncLockRSet(ctx, options, key, valueLength, value, expire,  database, password, timeout);
+    SyncLockRSet(ctx, options, key, valueLength, value, database, expire, password, timeout);
     returnLength = valueLength;
     returnValue = (char*)allocateAndCopy(value, valueLength);
 }
 ECL_REDIS_API void ECL_REDIS_CALL SyncLockRSetUChar(ICodeContext * ctx, size32_t & returnLength, UChar * & returnValue, const char * key, size32_t valueLength, const UChar * value, const char * options, unsigned __int64 database, unsigned expire, const char * password, unsigned __int64 timeout)
 {
     unsigned valueSize = (valueLength)*sizeof(UChar);
-    SyncLockRSet(ctx, options, key, valueSize, (char*)value, expire, database, password, timeout);
+    SyncLockRSet(ctx, options, key, valueSize, (char*)value, database, expire, password, timeout);
     returnLength= valueLength;
     returnValue = (UChar*)allocateAndCopy(value, valueSize);
 }
 ECL_REDIS_API void ECL_REDIS_CALL SyncLockRSetUtf8(ICodeContext * ctx, size32_t & returnLength, char * & returnValue, const char * key, size32_t valueLength, const char * value, const char * options, unsigned __int64 database, unsigned expire, const char * password, unsigned __int64 timeout)
 {
     unsigned valueSize = rtlUtf8Size(valueLength, value);
-    SyncLockRSet(ctx, options, key, valueSize, value, expire, database, password, timeout);
+    SyncLockRSet(ctx, options, key, valueSize, value, database, expire, password, timeout);
     returnLength = valueLength;
     returnValue = (char*)allocateAndCopy(value, valueSize);
 }

+ 51 - 0
testing/regress/ecl/key/redislockingtest.xml

@@ -76,3 +76,54 @@
 <Dataset name='Result 26'>
  <Row><value>RedisPlugin: ERROR - GET timed out.</value></Row>
 </Dataset>
+<Dataset name='Result 27'>
+ <Row><Result_27>databaseThenExpire</Result_27></Row>
+</Dataset>
+<Dataset name='Result 28'>
+ <Row><Result_28>databaseThenExpire</Result_28></Row>
+</Dataset>
+<Dataset name='Result 29'>
+ <Row><Result_29>databaseThenExpire</Result_29></Row>
+</Dataset>
+<Dataset name='Result 30'>
+ <Row><Result_30>databaseThenExpire</Result_30></Row>
+</Dataset>
+<Dataset name='Result 31'>
+ <Row><Result_31>databaseThenExpire</Result_31></Row>
+</Dataset>
+<Dataset name='Result 32'>
+ <Row><Result_32>databaseThenExpire</Result_32></Row>
+</Dataset>
+<Dataset name='Result 33'>
+ <Row><Result_33>databaseThenExpire</Result_33></Row>
+</Dataset>
+<Dataset name='Result 34'>
+ <Row><Result_34>databaseThenExpire</Result_34></Row>
+</Dataset>
+<Dataset name='Result 35'>
+ <Row><Result_35>databaseThenExpire</Result_35></Row>
+</Dataset>
+<Dataset name='Result 36'>
+ <Row><Result_36>Good boy Einnie!</Result_36></Row>
+</Dataset>
+<Dataset name='Result 37'>
+ <Row><Result_37>Good boy Einnie!</Result_37></Row>
+</Dataset>
+<Dataset name='Result 38'>
+ <Row><Result_38>Good boy Einnie!</Result_38></Row>
+</Dataset>
+<Dataset name='Result 39'>
+ <Row><Result_39>Good boy Einnie!</Result_39></Row>
+</Dataset>
+<Dataset name='Result 40'>
+ <Row><Result_40>supercalifragilisticexpialidocious</Result_40></Row>
+</Dataset>
+<Dataset name='Result 41'>
+ <Row><Result_41>supercalifragilisticexpialidocious</Result_41></Row>
+</Dataset>
+<Dataset name='Result 42'>
+ <Row><Result_42>supercalifragilisticexpialidocious</Result_42></Row>
+</Dataset>
+<Dataset name='Result 43'>
+ <Row><Result_43>supercalifragilisticexpialidocious</Result_43></Row>
+</Dataset>

+ 4 - 1
testing/regress/ecl/key/redissynctest.xml

@@ -89,8 +89,11 @@
  <Row><Result_30>300</Result_30></Row>
 </Dataset>
 <Dataset name='Result 31'>
- <Row><value>Redis Plugin: server authentication failed - NOAUTH Authentication required.</value></Row>
+ <Row><value>Auth Failed</value></Row>
 </Dataset>
 <Dataset name='Result 32'>
  <Row><value>Redis Plugin: ERROR - the requested key &apos;authTest1&apos; does not exist on database 0</value></Row>
 </Dataset>
+<Dataset name='Result 33'>
+ <Row><value>Redis Plugin: Connection failed - Connection refused for 127.0.0.1:9999</value></Row>
+</Dataset>

+ 44 - 0
testing/regress/ecl/redislockingtest.ecl

@@ -135,3 +135,47 @@ SEQUENTIAL(
     myRedis.SetString('channelTest2', 'redis_ecl_lock_channelTest2_0');
     OUTPUT(CATCH(ds2, ONFAIL(TRANSFORM({ STRING value }, SELF.value := FAILMESSAGE))));
     );
+
+SEQUENTIAL(
+    myRedis.FlushDB(1);
+    myRedis.SetAndPublishString('testDatabaseExpire1', 'databaseThenExpire', 1, 10000);
+    myRedis.GetString('testDatabaseExpire1', 1);
+    myRedis.GetOrLockString('testDatabaseExpire1', 1);
+    myRedis.FlushDB(1);
+    );
+
+SEQUENTIAL(
+    myRedis.FlushDB(2);
+    myRedis.SetAndPublishUnicode('testDatabaseExpire2', 'databaseThenExpire', 2, 10000);
+    myRedis.GetUnicode('testDatabaseExpire2', 2);
+    myRedis.GetOrLockUnicode('testDatabaseExpire2', 2);
+    myRedis.FlushDB(2);
+    );
+
+SEQUENTIAL(
+    myRedis.FlushDB(3);
+    myRedis.SetAndPublishUtf8('testDatabaseExpire3', 'databaseThenExpire', 3, 10000);
+    myRedis.GetUtf8('testDatabaseExpire3', 3);
+    myRedis.GetOrLockUtf8('testDatabaseExpire3', 3);
+    myRedis.FlushDB(3);
+    );
+
+SEQUENTIAL(
+    myRedis.FlushDB(),
+    myRedis.SetAndPublishString('t1', 'Good boy Einnie!');
+    myRedis.GetString('t1');
+
+    myRedis.SetAndPublishString('t2', 'Good boy Einnie!', 1, 10000);
+    myRedis.GetString('t2', 1);
+
+    myRedis.SetAndPublishString('t3', 'supercalifragilisticexpialidocious');
+    myRedis.GetString('t3');
+
+    myRedis.SetAndPublishString('t4', 'supercalifragilisticexpialidocious', 1, 10000);
+    myRedis.GetString('t4', 1);
+
+    myRedis.FlushDB();
+    myRedis.FlushDB(1);
+    );
+
+myRedis.FlushDB();

+ 11 - 2
testing/regress/ecl/redissynctest.ecl

@@ -157,10 +157,12 @@ SEQUENTIAL(
 
 //Test some authentication exceptions
 myRedis4 := RedisServer(server);
+STRING noauth := 'Redis Plugin: server authentication failed - NOAUTH Authentication required.';
+STRING opNotPerm :=  'Redis Plugin: server authentication failed - ERR operation not permitted';
 ds1 := DATASET(NOFOLD(1), TRANSFORM({string value}, SELF.value := myRedis4.GetString('authTest' + (string)COUNTER)));
 SEQUENTIAL(
     myRedis.FlushDB();
-    OUTPUT(CATCH(ds1, ONFAIL(TRANSFORM({ STRING value }, SELF.value := FAILMESSAGE))));
+    OUTPUT(CATCH(ds1, ONFAIL(TRANSFORM({ STRING value }, SELF.value := IF(FAILMESSAGE = noauth OR FAILMESSAGE = opNotPerm, 'Auth Failed', 'Unexpected Error')))));
     );
 
 ds2 := DATASET(NOFOLD(1), TRANSFORM({string value}, SELF.value := myRedis.GetString('authTest' + (string)COUNTER)));
@@ -168,6 +170,13 @@ SEQUENTIAL(
     myRedis.FlushDB();
     OUTPUT(CATCH(ds2, ONFAIL(TRANSFORM({ STRING value }, SELF.value := FAILMESSAGE))));
     );
-    
+
+myRedis5 := RedisServer('--SERVER=127.0.0.1:9999');
+ds3 := DATASET(NOFOLD(1), TRANSFORM({string value}, SELF.value := myRedis5.GetString('connectTest' + (string)COUNTER)));
+SEQUENTIAL(
+    myRedis.FlushDB();
+    OUTPUT(CATCH(ds3, ONFAIL(TRANSFORM({ STRING value }, SELF.value := FAILMESSAGE))));
+    );
+
 myRedis.FlushDB();
 myRedis2.FlushDB();