Sfoglia il codice sorgente

Merge branch 'ecl_querysets' of https://github.com/afishbeck/HPCC-Platform into afishbeck-ecl_querysets

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 13 anni fa
parent
commit
ecc98b8bd5

+ 1 - 0
ecl/eclcmd/CMakeLists.txt

@@ -23,6 +23,7 @@
 #    Cmake Input File for ecl
 #####################################################
 
+add_subdirectory (queries)
 
 project( ecl )
 

+ 13 - 0
ecl/eclcmd/eclcmd_common.cpp

@@ -27,6 +27,19 @@
 #include "ws_workunits.hpp"
 #include "eclcmd_common.hpp"
 
+void outputMultiExceptions(const IMultiException &me)
+{
+    fprintf(stderr, "\nException(s):\n");
+    aindex_t count = me.ordinality();
+    for (aindex_t i=0; i<count; i++)
+    {
+        IException& e = me.item(i);
+        StringBuffer msg;
+        fprintf(stderr, "%d: %s\n", e.errorCode(), e.errorMessage(msg).str());
+    }
+    fprintf(stderr, "\n");
+}
+
 bool extractEclCmdOption(StringBuffer & option, IProperties * globals, const char * envName, const char * propertyName, const char * defaultPrefix, const char * defaultSuffix)
 {
     if (option.length())        // check if already specified via a command line option

+ 4 - 0
ecl/eclcmd/eclcmd_common.hpp

@@ -77,6 +77,7 @@ typedef IEclCommand *(*EclCommandFactory)(const char *cmdname);
 #define ECLOPT_QUERYSET "--queryset"
 #define ECLOPT_QUERYSET_S "-qs"
 #define ECLOPT_VERSION "--version"
+#define ECLOPT_SHOW "--show"
 
 #define ECLOPT_LIB_PATH_S "-L"
 #define ECLOPT_IMP_PATH_S "-I"
@@ -210,4 +211,7 @@ public:
     StringAttr optQuerySet;
     StringAttr optQuery;
 };
+
+void outputMultiExceptions(const IMultiException &me);
+
 #endif

+ 114 - 0
ecl/eclcmd/eclcmd_core.cpp

@@ -40,6 +40,120 @@ void outputMultiExceptions(const IMultiException &me)
     fprintf(stderr, "\n");
 }
 
+class ConvertEclParameterToArchive
+{
+public:
+    ConvertEclParameterToArchive(EclCmdWithEclTarget &_cmd) : cmd(_cmd)
+    {
+    }
+
+    void appendOptPath(StringBuffer &cmdLine, const char opt, const char *path)
+    {
+        if (!path || !*path)
+            return;
+        if (*path==';')
+            path++;
+        cmdLine.append(" -").append(opt).append(path);
+    }
+
+    void buildCmd(StringBuffer &cmdLine)
+    {
+        cmdLine.set("eclcc -E");
+        appendOptPath(cmdLine, 'I', cmd.optImpPath.str());
+        appendOptPath(cmdLine, 'L', cmd.optLibPath.str());
+        if (cmd.optManifest.length())
+            cmdLine.append(" -manifest ").append(cmd.optManifest.get());
+        if (streq(cmd.optObj.value.sget(), "stdin"))
+            cmdLine.append(" - ");
+        else
+            cmdLine.append(" ").append(cmd.optObj.value.get());
+    }
+
+    bool eclcc(StringBuffer &out)
+    {
+        StringBuffer cmdLine;
+        buildCmd(cmdLine);
+
+        Owned<IPipeProcess> pipe = createPipeProcess();
+        bool hasInput = streq(cmd.optObj.value.sget(), "stdin");
+        pipe->run(cmd.optVerbose ? "EXEC" : NULL, cmdLine.str(), NULL, hasInput, true, true);
+
+        StringBuffer errors;
+        Owned<EclCmdErrorReader> errorReader = new EclCmdErrorReader(pipe, errors);
+        errorReader->start();
+
+        if (pipe->hasInput())
+        {
+            pipe->write(cmd.optObj.mb.length(), cmd.optObj.mb.toByteArray());
+            pipe->closeInput();
+        }
+        if (pipe->hasOutput())
+        {
+           byte buf[4096];
+           loop
+           {
+                size32_t read = pipe->read(sizeof(buf),buf);
+                if (!read)
+                    break;
+                out.append(read, (const char *) buf);
+            }
+        }
+        int retcode = pipe->wait();
+        errorReader->join();
+
+        if (errors.length())
+            fprintf(stderr, "%s\n", errors.str());
+
+        return (retcode == 0);
+    }
+
+    bool process()
+    {
+        if (cmd.optObj.type!=eclObjSource || cmd.optObj.value.isEmpty())
+            return false;
+
+        StringBuffer output;
+        if (eclcc(output) && output.length() && isArchiveQuery(output.str()))
+        {
+            cmd.optObj.type = eclObjArchive;
+            cmd.optObj.mb.clear().append(output.str());
+            return true;
+        }
+        fprintf(stderr,"\nError creating archive\n");
+        return false;
+    }
+
+private:
+    EclCmdWithEclTarget &cmd;
+
+    class EclCmdErrorReader : public Thread
+    {
+    public:
+        EclCmdErrorReader(IPipeProcess *_pipe, StringBuffer &_errs)
+            : Thread("EclToArchive::ErrorReader"), pipe(_pipe), errs(_errs)
+        {
+        }
+
+        virtual int run()
+        {
+           byte buf[4096];
+           loop
+           {
+                size32_t read = pipe->readError(sizeof(buf), buf);
+                if (!read)
+                    break;
+                errs.append(read, (const char *) buf);
+            }
+            return 0;
+        }
+    private:
+        IPipeProcess *pipe;
+        StringBuffer &errs;
+    };
+};
+
+
+
 bool doDeploy(EclCmdWithEclTarget &cmd, IClientWsWorkunits *client, const char *cluster, const char *name, StringBuffer *wuid, bool noarchive, bool displayWuid=true)
 {
     StringBuffer s;

+ 53 - 0
ecl/eclcmd/queries/CMakeLists.txt

@@ -0,0 +1,53 @@
+##############################################################################
+# Copyright (C) 2012 HPCC Systems.
+##############################################################################
+
+
+# Component: ecl-queries
+#####################################################
+# Description:
+# ------------
+#    Cmake Input File for ecl-queries
+#####################################################
+
+
+project( ecl-queries )
+
+include(${HPCC_SOURCE_DIR}/esp/scm/smcscm.cmake)
+
+set (    SRCS
+         ${ESPSCM_GENERATED_DIR}/ws_workunits_esp.cpp
+         ecl-queries.cpp
+         ../eclcmd_shell.cpp
+         ../eclcmd_common.hpp
+         ../eclcmd_common.cpp
+    )
+
+include_directories (
+         ${CMAKE_BINARY_DIR}
+         ${CMAKE_BINARY_DIR}/oss
+         ${HPCC_SOURCE_DIR}/system/include
+         ${HPCC_SOURCE_DIR}/system/jlib
+         ${HPCC_SOURCE_DIR}/common/workunit
+         ${HPCC_SOURCE_DIR}/esp/clients
+         ${HPCC_SOURCE_DIR}/esp/bindings
+         ${HPCC_SOURCE_DIR}/esp/bindings/SOAP/xpp
+         ${HPCC_SOURCE_DIR}/esp/platform
+         ${HPCC_SOURCE_DIR}/system/security/shared
+         ${HPCC_SOURCE_DIR}/system/include
+         ${HPCC_SOURCE_DIR}/system/xmllib
+         ${HPCC_SOURCE_DIR}/ecl/eclcmd
+    )
+
+ADD_DEFINITIONS( -D_CONSOLE )
+
+add_executable ( ecl-queries ${SRCS} )
+add_dependencies ( ecl-queries espscm ws_workunits )
+install ( TARGETS ecl-queries DESTINATION ${DIR_NAME}/bin )
+target_link_libraries ( ecl-queries
+        jlib
+        esphttp
+        workunit
+    )
+
+install ( PROGRAMS ecl-queries.install DESTINATION ${OSSDIR}/etc/init.d/install COMPONENT Runtime )

+ 280 - 0
ecl/eclcmd/queries/ecl-queries.cpp

@@ -0,0 +1,280 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+#include <stdio.h>
+#include "jlog.hpp"
+#include "jfile.hpp"
+#include "jargv.hpp"
+
+#include "build-config.h"
+
+#include "ws_workunits.hpp"
+
+#include "eclcmd.hpp"
+#include "eclcmd_common.hpp"
+#include "eclcmd_core.hpp"
+
+#define INIFILE "ecl.ini"
+#define SYSTEMCONFDIR CONFIG_DIR
+#define DEFAULTINIFILE "ecl.ini"
+#define SYSTEMCONFFILE ENV_CONF_FILE
+
+//=========================================================================================
+
+class ActiveQueryMap
+{
+public:
+    ActiveQueryMap(IConstWUQuerySetDetail &qs) : queryMap(createPTree())
+    {
+        IArrayOf<IConstQuerySetAlias> &aliases = qs.getAliases();
+        ForEachItemIn(i, aliases)
+            addMappedAlias(aliases.item(i).getId(), aliases.item(i).getName());
+    }
+
+    void addMappedAlias(const char *queryid, const char *alias)
+    {
+        if (queryid && *queryid && alias && *alias)
+            ensureMappedQuery(queryid)->addProp("Alias", alias);
+    }
+
+    IPropertyTree *ensureMappedQuery(const char *queryid)
+    {
+        VStringBuffer xpath("Query[@id='%s']", queryid);
+        IPropertyTree *query = queryMap->getPropTree(xpath.str());
+        if (!query)
+        {
+            query = queryMap->addPropTree("Query", createPTree());
+            query->setProp("@id", queryid);
+        }
+        return query;
+    }
+
+    bool isActive(const char *queryid)
+    {
+        VStringBuffer xpath("Query[@id='%s']", queryid);
+        return queryMap->hasProp(xpath.str());
+    }
+
+    IPropertyTreeIterator *getActiveNames(const char *queryid)
+    {
+        VStringBuffer xpath("Query[@id='%s']/Alias", queryid);
+        return queryMap->getElements(xpath.str());
+    }
+
+private:
+    Linked<IPropertyTree> queryMap;
+};
+
+
+
+#define QUERYLIST_SHOW_UNFLAGGED            0x01
+#define QUERYLIST_SHOW_ACTIVE               0x02
+#define QUERYLIST_SHOW_SUSPENDED            0x04
+#define QUERYLIST_SHOW_CLUSTER_SUSPENDED    0x08
+
+class EclCmdQueriesList : public EclCmdCommon
+{
+public:
+    EclCmdQueriesList() : flags(0)
+    {
+    }
+    virtual bool parseCommandLineOptions(ArgvIterator &iter)
+    {
+        for (; !iter.done(); iter.next())
+        {
+            const char *arg = iter.query();
+            if (*arg!='-')
+            {
+                optQuerySet.set(arg);
+                continue;
+            }
+            if (iter.matchOption(optCluster, ECLOPT_CLUSTER) || iter.matchOption(optCluster, ECLOPT_CLUSTER_S))
+                continue;
+            StringAttr temp;
+            if (iter.matchOption(temp, ECLOPT_SHOW))
+            {
+                for (const char *ch = temp.sget(); *ch; ch++)
+                {
+                    switch (*ch)
+                    {
+                    case 'A':
+                        flags |= QUERYLIST_SHOW_ACTIVE;
+                        break;
+                    case 'S':
+                        flags |= QUERYLIST_SHOW_SUSPENDED;
+                        break;
+                    case 'X':
+                        flags |= QUERYLIST_SHOW_CLUSTER_SUSPENDED;
+                        break;
+                    case 'U':
+                        flags |= QUERYLIST_SHOW_UNFLAGGED;
+                        break;
+                    default:
+                        fprintf(stderr, "Unrecognized --show flag = %c", *ch);
+                        return false;
+                    }
+                }
+                continue;
+            }
+            if (EclCmdCommon::matchCommandLineOption(iter, true)!=EclCmdOptionMatch)
+                return false;
+        }
+        return true;
+    }
+    virtual bool finalizeOptions(IProperties *globals)
+    {
+        if (!EclCmdCommon::finalizeOptions(globals))
+            return false;
+        return true;
+    }
+
+    void outputQuery(IConstQuerySetQuery &query, ActiveQueryMap &queryMap)
+    {
+        const char *queryid = query.getId();
+        bool isActive = queryMap.isActive(queryid);
+        if (flags)
+        {
+            if (isActive && !(flags & QUERYLIST_SHOW_ACTIVE))
+                return;
+            if (query.getSuspended() && !(flags & QUERYLIST_SHOW_SUSPENDED))
+                return;
+            if (!isActive && !query.getSuspended() &&  !(flags & QUERYLIST_SHOW_UNFLAGGED))
+                return;
+        }
+        VStringBuffer line("  %c%c  ", query.getSuspended() ? 'S' : ' ', isActive ? 'A' : ' ');
+        line.append(queryid);
+        if (isActive)
+        {
+            Owned<IPropertyTreeIterator> activeNames = queryMap.getActiveNames(queryid);
+            if (line.length() < 35)
+                line.appendN(35 - line.length(), ' ');
+            line.append("[");
+            activeNames->first();
+            while (activeNames->isValid())
+            {
+                line.append(activeNames->query().queryProp(NULL));
+                if (activeNames->next())
+                    line.append(',');
+            }
+            line.append("]");
+        }
+        fputs(line.append('\n').str(), stdout);
+    }
+
+    void outputQueryset(IConstWUQuerySetDetail &qs)
+    {
+        ActiveQueryMap queryMap(qs);
+        if (qs.getQuerySetName())
+            fprintf(stdout, "\nQuerySet: %s\n", qs.getQuerySetName());
+        fputs("\nFlags Query Id                     [Active Name(s)]\n", stdout);
+        fputs("----- ---------------------------- ----------------\n", stdout);
+
+        IArrayOf<IConstQuerySetQuery> &queries = qs.getQueries();
+        ForEachItemIn(id, queries)
+            outputQuery(queries.item(id), queryMap);
+    }
+
+    virtual int processCMD()
+    {
+        Owned<IClientWsWorkunits> client = createWsWorkunitsClient();
+        VStringBuffer url("http://%s:%s/WsWorkunits", optServer.sget(), optPort.sget());
+        client->addServiceUrl(url.str());
+        if (optUsername.length())
+            client->setUsernameToken(optUsername.get(), optPassword.sget(), NULL);
+
+        Owned<IClientWUMultiQuerySetDetailsRequest> req = client->createWUMultiQuerysetDetailsRequest();
+        req->setQuerySetName(optQuerySet.get());
+        req->setClusterName(optCluster.get());
+        req->setFilterType("All");
+
+        Owned<IClientWUMultiQuerySetDetailsResponse> resp = client->WUMultiQuerysetDetails(req);
+        if (resp->getExceptions().ordinality())
+            outputMultiExceptions(resp->getExceptions());
+        else
+        {
+            IArrayOf<IConstWUQuerySetDetail> &querysets = resp->getQuerysets();
+            ForEachItemIn(i, querysets)
+                outputQueryset(querysets.item(i));
+        }
+        return 0;
+    }
+    virtual void usage()
+    {
+        fprintf(stdout,"\nUsage:\n\n"
+            "ecl queries list [<queryset>][--cluster=<cluster>][--show=<flags>]\n\n"
+            " Options:\n"
+            "   <queryset>             name of queryset to get list of queries for\n"
+            "   -cl, --cluster=<name>  name of cluster to get list of published queries for\n"
+            "   --show=<flags>         show only queries with matching flags\n"
+            " Flags:\n"
+            "   A                      query is active\n"
+            "   S                      query is suspended in queryset\n"
+//not yet   "   X                      query is suspended on selected cluster\n"
+            "   U                      query with no flags set\n"
+            " Common Options:\n"
+        );
+        EclCmdCommon::usage();
+    }
+private:
+    StringAttr optCluster;
+    StringAttr optQuerySet;
+    unsigned flags;
+};
+
+IEclCommand *createEclQueriesCommand(const char *cmdname)
+{
+    if (!cmdname || !*cmdname)
+        return NULL;
+    if (strieq(cmdname, "list"))
+        return new EclCmdQueriesList();
+    return NULL;
+}
+
+//=========================================================================================
+
+class EclQueriesCMDShell : public EclCMDShell
+{
+public:
+    EclQueriesCMDShell(int argc, const char *argv[], EclCommandFactory _factory, const char *_version)
+        : EclCMDShell(argc, argv, _factory, _version)
+    {
+    }
+
+    virtual void usage()
+    {
+        fprintf(stdout,"\nUsage:\n\n"
+            "ecl queries <command> [command options]\n\n"
+            "   Queries Commands:\n"
+            "      list         list queries in queryset(s)\n"
+        );
+    }
+};
+
+static int doMain(int argc, const char *argv[])
+{
+    EclQueriesCMDShell processor(argc, argv, createEclQueriesCommand, BUILD_TAG);
+    return processor.run();
+}
+
+int main(int argc, const char *argv[])
+{
+    InitModuleObjects();
+    queryStderrLogMsgHandler()->setMessageFields(0);
+    unsigned exitCode = doMain(argc, argv);
+    releaseAtoms();
+    exit(exitCode);
+}

+ 1 - 0
ecl/eclcmd/queries/ecl-queries.install

@@ -0,0 +1 @@
+installFile "$binPath/ecl-queries" "/usr/bin/ecl-queries" 1 || exit 1

+ 27 - 0
ecl/eclcmd/queries/sourcedoc.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+################################################################################
+#    Copyright (C) 2011 HPCC Systems.
+#
+#    All rights reserved. This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+################################################################################
+-->
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+<section>
+    <title>ecl/ecl</title>
+
+    <para>
+        The ecl/ecl directory contains the sources for the ecl-queries command line tool.
+    </para>
+</section>

+ 31 - 0
esp/scm/ws_workunits.ecm

@@ -1064,6 +1064,12 @@ ESPresponse [exceptions_inline] WUQuerysetsResponse
     ESParray<ESPstruct QuerySet> Querysets;
 };
 
+ESPStruct ClusterQueryState
+{
+    string Cluster;
+    bool Suspended;
+};
+
 ESPStruct QuerySetQuery
 {
     string Id;
@@ -1071,6 +1077,7 @@ ESPStruct QuerySetQuery
     string Wuid;
     string Dll;
     bool Suspended;
+    ESParray<ESPstruct ClusterQueryState> Clusters;
 };
 
 ESPStruct QuerySetAlias
@@ -1101,6 +1108,29 @@ ESPresponse [exceptions_inline] WUQuerySetDetailsResponse
     ESParray<ESPstruct QuerySetAlias> QuerysetAliases;
 };
 
+ESPrequest WUMultiQuerySetDetailsRequest
+{
+    string  ClusterName;
+    string  QuerySetName;
+    string  Filter;
+    ESPenum WUQuerySetFilterType FilterType("All");
+};
+
+ESPstruct WUQuerySetDetail
+{
+    string  QuerySetName;
+    ESParray<ESPstruct QuerySetQuery> Queries;
+    ESParray<ESPstruct QuerySetAlias> Aliases;
+};
+
+
+ESPresponse [exceptions_inline] WUMultiQuerySetDetailsResponse
+{
+    string ClusterName;
+    ESParray<ESPstruct WUQuerySetDetail> Querysets;
+};
+
+
 ESPenum QuerySetQueryActionTypes : string
 {
     Suspend("Suspend"),
@@ -1238,6 +1268,7 @@ ESPservice [
     ESPmethod [resp_xsl_default("/esp/xslt/WUPublishWorkunit.xslt")] WUPublishWorkunit(WUPublishWorkunitRequest, WUPublishWorkunitResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/WUQuerysets.xslt")] WUQuerysets(WUQuerysetsRequest, WUQuerysetsResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/WUQuerysetQueries.xslt")] WUQuerysetDetails(WUQuerySetDetailsRequest, WUQuerySetDetailsResponse);
+    ESPmethod WUMultiQuerysetDetails(WUMultiQuerySetDetailsRequest, WUMultiQuerySetDetailsResponse);
     ESPmethod WUQuerysetQueryAction(WUQuerySetQueryActionRequest, WUQuerySetQueryActionResponse);
     ESPmethod WUQuerysetAliasAction(WUQuerySetAliasActionRequest, WUQuerySetAliasActionResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/WUCopyLogicalFiles.xslt")] WUCopyLogicalFiles(WUCopyLogicalFilesRequest, WUCopyLogicalFilesResponse);

+ 181 - 129
esp/services/ws_workunits/ws_workunitsQuerySets.cpp

@@ -172,105 +172,6 @@ void copyWULogicalFilesToTarget(IEspContext &context, IConstWUClusterInfo &clust
     }
 }
 
-void gatherQuerySetQueryDetails(IPropertyTree *query, IEspQuerySetQuery *queryInfo)
-{
-    queryInfo->setId(query->queryProp("@id"));
-    queryInfo->setName(query->queryProp("@name"));
-    queryInfo->setDll(query->queryProp("@dll"));
-    queryInfo->setWuid(query->queryProp("@wuid"));
-    queryInfo->setSuspended(query->getPropBool("@suspended", false));
-}
-
-void gatherQuerySetAliasDetails(IPropertyTree *alias, IEspQuerySetAlias *aliasInfo)
-{
-    aliasInfo->setName(alias->queryProp("@name"));
-    aliasInfo->setId(alias->queryProp("@id"));
-}
-
-void retrieveAllQuerysetDetails(IPropertyTree *registry, IEspWUQuerySetDetailsResponse &resp)
-{
-    IArrayOf<IEspQuerySetQuery> querySetQueries;
-    Owned<IPropertyTreeIterator> queries = registry->getElements("Query");
-    ForEach(*queries)
-    {
-        IPropertyTree &query = queries->query();
-        Owned<IEspQuerySetQuery> q = createQuerySetQuery("", "");
-        gatherQuerySetQueryDetails(&query, q);
-        querySetQueries.append(*q.getClear());
-    }
-    resp.setQuerysetQueries(querySetQueries);
-
-    IArrayOf<IEspQuerySetAlias> querySetAliases;
-    Owned<IPropertyTreeIterator> aliases = registry->getElements("Alias");
-    ForEach(*aliases)
-    {
-        IPropertyTree &alias = aliases->query();
-        Owned<IEspQuerySetAlias> a = createQuerySetAlias("", "");
-        gatherQuerySetAliasDetails(&alias, a);
-        querySetAliases.append(*a.getClear());
-    }
-}
-
-void retrieveQuerysetDetailsFromAlias(IPropertyTree *registry, const char *filter, IEspWUQuerySetDetailsResponse &resp)
-{
-    StringBuffer xpath;
-    xpath.append("Alias[@name='").append(filter).append("']");
-    IPropertyTree *alias = registry->queryPropTree(xpath);
-    if (!alias)
-    {
-        DBGLOG("Alias %s not found", filter);
-        return;
-    }
-
-    IArrayOf<IEspQuerySetAlias> querySetAliases;
-    Owned<IEspQuerySetAlias> a = createQuerySetAlias("", "");
-    gatherQuerySetAliasDetails(alias, a);
-    querySetAliases.append(*a.getClear());
-
-    xpath.clear().append("Query[@id='").append(a->getId()).append("']");
-    IPropertyTree *query = registry->queryPropTree(xpath);
-    if (!query)
-    {
-        DBGLOG("No matching Query %s found for Alias %s", a->getId(), filter);
-        return;
-    }
-
-    IArrayOf<IEspQuerySetQuery> querySetQueries;
-    Owned<IEspQuerySetQuery> q = createQuerySetQuery("", "");
-    gatherQuerySetQueryDetails(query, q);
-    querySetQueries.append(*q.getClear());
-    resp.setQuerysetQueries(querySetQueries);
-}
-
-void retrieveQuerysetDetailsFromQuery(IPropertyTree *registry, const char *filter, const char *filterType, IEspWUQuerySetDetailsResponse &resp)
-{
-    StringBuffer xpath;
-    xpath.clear().append("Query[@").append(filterType).append("='").append(filter).append("']");
-    IPropertyTree *query = registry->queryPropTree(xpath);
-    if (!query)
-    {
-        DBGLOG("No matching Query %s found for %s", filter, filterType);
-        return;
-    }
-
-    IArrayOf<IEspQuerySetQuery> querySetQueries;
-    Owned<IEspQuerySetQuery> q = createQuerySetQuery("", "");
-    gatherQuerySetQueryDetails(query, q);
-    querySetQueries.append(*q.getClear());
-    resp.setQuerysetQueries(querySetQueries);
-
-    IArrayOf<IEspQuerySetAlias> querySetAliases;
-    xpath.clear().append("Alias[@id='").append(q->getId()).append("']");
-    Owned<IPropertyTreeIterator> aliases = registry->getElements(xpath.str());
-    ForEach(*aliases)
-    {
-        IPropertyTree &alias = aliases->query();
-        Owned<IEspQuerySetAlias> a = createQuerySetAlias("", "");
-        gatherQuerySetAliasDetails(&alias, a);
-        querySetAliases.append(*a.getClear());
-    }
-}
-
 bool CWsWorkunitsEx::onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogicalFilesRequest &req, IEspWUCopyLogicalFilesResponse &resp)
 {
     if (isEmpty(req.getWuid()))
@@ -374,6 +275,165 @@ bool CWsWorkunitsEx::onWUQuerysets(IEspContext &context, IEspWUQuerysetsRequest
     return true;
 }
 
+void gatherQuerySetQueryDetails(IPropertyTree *query, IEspQuerySetQuery *queryInfo)
+{
+    queryInfo->setId(query->queryProp("@id"));
+    queryInfo->setName(query->queryProp("@name"));
+    queryInfo->setDll(query->queryProp("@dll"));
+    queryInfo->setWuid(query->queryProp("@wuid"));
+    queryInfo->setSuspended(query->getPropBool("@suspended", false));
+}
+
+void gatherQuerySetAliasDetails(IPropertyTree *alias, IEspQuerySetAlias *aliasInfo)
+{
+    aliasInfo->setName(alias->queryProp("@name"));
+    aliasInfo->setId(alias->queryProp("@id"));
+}
+
+void retrieveAllQuerysetDetails(IPropertyTree *registry, IArrayOf<IEspQuerySetQuery> &queries, IArrayOf<IEspQuerySetAlias> &aliases)
+{
+    Owned<IPropertyTreeIterator> regQueries = registry->getElements("Query");
+    ForEach(*regQueries)
+    {
+        IPropertyTree &query = regQueries->query();
+        Owned<IEspQuerySetQuery> q = createQuerySetQuery();
+        gatherQuerySetQueryDetails(&query, q);
+        queries.append(*q.getClear());
+    }
+
+    Owned<IPropertyTreeIterator> regAliases = registry->getElements("Alias");
+    ForEach(*regAliases)
+    {
+        IPropertyTree &alias = regAliases->query();
+        Owned<IEspQuerySetAlias> a = createQuerySetAlias();
+        gatherQuerySetAliasDetails(&alias, a);
+        aliases.append(*a.getClear());
+    }
+}
+
+void retrieveQuerysetDetailsFromAlias(IPropertyTree *registry, const char *name, IArrayOf<IEspQuerySetQuery> &queries, IArrayOf<IEspQuerySetAlias> &aliases)
+{
+    StringBuffer xpath;
+    xpath.append("Alias[@name='").append(name).append("']");
+    IPropertyTree *alias = registry->queryPropTree(xpath);
+    if (!alias)
+    {
+        DBGLOG("Alias %s not found", name);
+        return;
+    }
+
+    Owned<IEspQuerySetAlias> a = createQuerySetAlias();
+    gatherQuerySetAliasDetails(alias, a);
+    xpath.clear().append("Query[@id='").append(a->getId()).append("']");
+    aliases.append(*a.getClear());
+
+    IPropertyTree *query = registry->queryPropTree(xpath);
+    if (!query)
+    {
+        DBGLOG("No matching Query %s found for Alias %s", a->getId(), name);
+        return;
+    }
+
+    Owned<IEspQuerySetQuery> q = createQuerySetQuery();
+    gatherQuerySetQueryDetails(query, q);
+    queries.append(*q.getClear());
+}
+
+void retrieveQuerysetDetailsFromQuery(IPropertyTree *registry, const char *value, const char *type, IArrayOf<IEspQuerySetQuery> &queries, IArrayOf<IEspQuerySetAlias> &aliases)
+{
+    if (!strieq(type, "Id") && !strieq(type, "Name"))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Unrecognized queryset filter type %s", type);
+
+    StringBuffer attributeName(type);
+    StringBuffer xpath;
+    xpath.clear().append("Query[@").append(attributeName.toLowerCase()).append("='").append(value).append("']");
+    IPropertyTree *query = registry->queryPropTree(xpath);
+    if (!query)
+    {
+        DBGLOG("No matching Query %s found for %s", value, type);
+        return;
+    }
+
+    Owned<IEspQuerySetQuery> q = createQuerySetQuery();
+    gatherQuerySetQueryDetails(query, q);
+    xpath.clear().append("Alias[@id='").append(q->getId()).append("']");
+    queries.append(*q.getClear());
+
+    Owned<IPropertyTreeIterator> regAliases = registry->getElements(xpath.str());
+    ForEach(*regAliases)
+    {
+        IPropertyTree &alias = regAliases->query();
+        Owned<IEspQuerySetAlias> a = createQuerySetAlias();
+        gatherQuerySetAliasDetails(&alias, a);
+        aliases.append(*a.getClear());
+    }
+}
+
+void retrieveQuerysetDetails(IPropertyTree *registry, const char *type, const char *value, IArrayOf<IEspQuerySetQuery> &queries, IArrayOf<IEspQuerySetAlias> &aliases)
+{
+    if (strieq(type, "All"))
+        return retrieveAllQuerysetDetails(registry, queries, aliases);
+    if (!value || !*value)
+        return;
+    if (strieq(type, "Alias"))
+        return retrieveQuerysetDetailsFromAlias(registry, value, queries, aliases);
+    return retrieveQuerysetDetailsFromQuery(registry, value, type, queries, aliases);
+}
+
+void retrieveQuerysetDetails(IArrayOf<IEspWUQuerySetDetail> &details, IPropertyTree *registry, const char *type, const char *value)
+{
+    if (!registry)
+        return;
+
+    IArrayOf<IEspQuerySetQuery> queries;
+    IArrayOf<IEspQuerySetAlias> aliases;
+    retrieveQuerysetDetails(registry, type, value, queries, aliases);
+
+    Owned<IEspWUQuerySetDetail> detail = createWUQuerySetDetail();
+    detail->setQuerySetName(registry->queryProp("@id"));
+    detail->setQueries(queries);
+    detail->setAliases(aliases);
+    details.append(*detail.getClear());
+}
+
+void retrieveQuerysetDetails(IArrayOf<IEspWUQuerySetDetail> &details, const char *queryset, const char *type, const char *value)
+{
+    if (!queryset || !*queryset)
+        return;
+    Owned<IPropertyTree> registry = getQueryRegistry(queryset, true);
+    if (!registry)
+        return;
+    retrieveQuerysetDetails(details, registry, type, value);
+}
+
+void retrieveQuerysetDetailsByCluster(IArrayOf<IEspWUQuerySetDetail> &details, const char *cluster, const char *queryset, const char *type, const char *value)
+{
+    Owned<IConstWUClusterInfo> info = getTargetClusterInfo(cluster);
+    if (!info)
+        throw MakeStringException(ECLWATCH_CANNOT_RESOLVE_CLUSTER_NAME, "Cluster %s not found", cluster);
+
+    SCMStringBuffer clusterQueryset;
+    info->getQuerySetName(clusterQueryset);
+    if (!clusterQueryset.length())
+        throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "No QuerySets found for cluster %s", cluster);
+    if (notEmpty(queryset) && !strieq(clusterQueryset.str(), queryset))
+        throw MakeStringException(ECLWATCH_QUERYSET_NOT_ON_CLUSTER, "Cluster %s not configured to load QuerySet %s", cluster, queryset);
+
+    retrieveQuerysetDetails(details, clusterQueryset.str(), type, value);
+}
+
+void retrieveAllQuerysetDetails(IArrayOf<IEspWUQuerySetDetail> &details, const char *type, const char *value)
+{
+    Owned<IPropertyTree> root = getQueryRegistryRoot();
+    if (!root)
+        throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "QuerySet Registry not found");
+    Owned<IPropertyTreeIterator> querysets = root->getElements("QuerySet");
+    if (!root)
+        throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "QuerySet Registry not found");
+    ForEach(*querysets)
+        retrieveQuerysetDetails(details, &querysets->query(), type, value);
+}
+
 bool CWsWorkunitsEx::onWUQuerysetDetails(IEspContext &context, IEspWUQuerySetDetailsRequest & req, IEspWUQuerySetDetailsResponse & resp)
 {
     resp.setQuerySetName(req.getQuerySetName());
@@ -382,38 +442,30 @@ bool CWsWorkunitsEx::onWUQuerysetDetails(IEspContext &context, IEspWUQuerySetDet
     if (!registry)
         return false;
 
-    const char *filterType = req.getFilterTypeAsString();
-    if (strieq(filterType, "All"))
-        retrieveAllQuerysetDetails(registry, resp);
-    else if (strieq(filterType, "Alias"))
-    {
-        const char *filter = req.getFilter();
-        if (!filter || !*filter)
-        {
-            DBGLOG("Need to specify an Alias name for lookup");
-            return false;
-        }
-        retrieveQuerysetDetailsFromAlias(registry, filter, resp);
-    }
+    IArrayOf<IEspQuerySetQuery> respQueries;
+    IArrayOf<IEspQuerySetAlias> respAliases;
+
+    retrieveQuerysetDetails(registry, req.getFilterTypeAsString(), req.getFilter(), respQueries, respAliases);
+
+    resp.setQuerysetQueries(respQueries);
+    resp.setQuerysetAliases(respAliases);
+
+    return true;
+}
+
+bool CWsWorkunitsEx::onWUMultiQuerysetDetails(IEspContext &context, IEspWUMultiQuerySetDetailsRequest & req, IEspWUMultiQuerySetDetailsResponse & resp)
+{
+    IArrayOf<IEspWUQuerySetDetail> respDetails;
+
+    if (notEmpty(req.getClusterName()))
+        retrieveQuerysetDetailsByCluster(respDetails, req.getClusterName(), req.getQuerySetName(), req.getFilterTypeAsString(), req.getFilter());
+    else if (notEmpty(req.getQuerySetName()))
+        retrieveQuerysetDetails(respDetails, req.getQuerySetName(), req.getFilterTypeAsString(), req.getFilter());
     else
-    {
-        const char *filter = req.getFilter();
-        if (!filter || !*filter)
-        {
-            DBGLOG("Need to specify an Alias name for lookup");
-            return false;
-        }
-        if (strieq(filterType, "Id"))
-            retrieveQuerysetDetailsFromQuery(registry, filter, "id", resp);
-        else if (strieq(filterType, "Name"))
-            retrieveQuerysetDetailsFromQuery(registry, filter, "name", resp);
-        else
-        {
-            DBGLOG("invalid filterType %s specified", filter);
-            return false;
-        }
+        retrieveAllQuerysetDetails(respDetails, req.getFilterTypeAsString(), req.getFilter());
+
+    resp.setQuerysets(respDetails);
 
-    }
     return true;
 }
 

+ 0 - 1
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -89,7 +89,6 @@ public:
     }
 };
 
-
 void submitWsWorkunit(IEspContext& context, IConstWorkUnit* cw, const char* cluster, const char* snapshot, int maxruntime, bool compile, bool resetWorkflow)
 {
     ensureWsWorkunitAccess(context, *cw, SecAccess_Write);

+ 1 - 0
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -42,6 +42,7 @@ public:
     bool onWUPublishWorkunit(IEspContext &context, IEspWUPublishWorkunitRequest & req, IEspWUPublishWorkunitResponse & resp);
     bool onWUQuerysets(IEspContext &context, IEspWUQuerysetsRequest & req, IEspWUQuerysetsResponse & resp);
     bool onWUQuerysetDetails(IEspContext &context, IEspWUQuerySetDetailsRequest & req, IEspWUQuerySetDetailsResponse & resp);
+    bool onWUMultiQuerysetDetails(IEspContext &context, IEspWUMultiQuerySetDetailsRequest &req, IEspWUMultiQuerySetDetailsResponse &resp);
     bool onWUQuerysetQueryAction(IEspContext &context, IEspWUQuerySetQueryActionRequest & req, IEspWUQuerySetQueryActionResponse & resp);
     bool onWUQuerysetAliasAction(IEspContext &context, IEspWUQuerySetAliasActionRequest &req, IEspWUQuerySetAliasActionResponse &resp);
     bool onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogicalFilesRequest &req, IEspWUCopyLogicalFilesResponse &resp);

+ 2 - 0
esp/smc/SMCLib/exception_util.hpp

@@ -117,6 +117,8 @@
 #define ECLWATCH_QUERYSET_NOT_FOUND         ECLWATCH_ERROR_START+96
 #define ECLWATCH_QUERYID_NOT_FOUND          ECLWATCH_ERROR_START+97
 #define ECLWATCH_ALIAS_NOT_FOUND            ECLWATCH_ERROR_START+98
+#define ECLWATCH_QUERYSET_NOT_ON_CLUSTER    ECLWATCH_ERROR_START+99
+
 
 inline void FORWARDEXCEPTION(IEspContext &context, IException *e, unsigned codeNew)
 {