Bläddra i källkod

Merge pull request #5977 from afishbeck/copyQuerySet

HPCC-11564 Add method to clone entire queryset from one target to another

Reviewed-By: Gavin Halliday <gavin.halliday@lexisnexis.com>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 11 år sedan
förälder
incheckning
e86e6073ca

+ 27 - 8
common/workunit/workunit.cpp

@@ -9786,7 +9786,7 @@ extern WORKUNIT_API IPropertyTree * getPackageSetRegistry(const char * wsEclId,
     return conn->getRoot();
 }
 
-void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const char *queryName, IPropertyTree *packageInfo, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid)
+void addQueryToQuerySet(IWorkUnit *workunit, IPropertyTree *queryRegistry, const char *queryName, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid)
 {
     StringBuffer cleanQueryName;
     appendUtf8XmlName(cleanQueryName, strlen(queryName), queryName);
@@ -9800,8 +9800,6 @@ void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const cha
     SCMStringBuffer wuid;
     workunit->getWuid(wuid);
 
-    Owned<IPropertyTree> queryRegistry = getQueryRegistry(querySetName, false);
-
     StringBuffer currentTargetClusterType;
     queryRegistry->getProp("@targetclustertype", currentTargetClusterType); 
 
@@ -9828,11 +9826,19 @@ void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const cha
     workunit->setIsQueryService(true); //will check querysets before delete
     workunit->commit();
 
+    activateQuery(queryRegistry, activateOption, queryName, newQueryId, userid);
+}
+
+void activateQuery(IPropertyTree *queryRegistry, WUQueryActivationOptions activateOption, const char *queryName, const char *queryId, const char *userid)
+{
+    StringBuffer cleanQueryName;
+    appendUtf8XmlName(cleanQueryName, strlen(queryName), queryName);
+
     if (activateOption == ACTIVATE_SUSPEND_PREVIOUS|| activateOption == ACTIVATE_DELETE_PREVIOUS)
     {
         Owned<IPropertyTree> prevQuery = resolveQueryAlias(queryRegistry, cleanQueryName);
-        setQueryAlias(queryRegistry, cleanQueryName, newQueryId);
-        if (prevQuery)
+        setQueryAlias(queryRegistry, cleanQueryName, queryId);
+        if (prevQuery && !streq(queryId, prevQuery->queryProp("@id")))
         {
             if (activateOption == ACTIVATE_SUSPEND_PREVIOUS)
                 setQuerySuspendedState(queryRegistry, prevQuery->queryProp("@id"), true, userid);
@@ -9841,7 +9847,13 @@ void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const cha
         }
     }
     else if (activateOption == MAKE_ACTIVATE || activateOption == MAKE_ACTIVATE_LOAD_DATA_ONLY)
-        setQueryAlias(queryRegistry, cleanQueryName, newQueryId.str());
+        setQueryAlias(queryRegistry, cleanQueryName, queryId);
+}
+
+void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const char *queryName, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid)
+{
+    Owned<IPropertyTree> queryRegistry = getQueryRegistry(querySetName, false);
+    addQueryToQuerySet(workunit, queryRegistry, queryName, activateOption, newQueryId, userid);
 }
 
 bool removeQuerySetAlias(const char *querySetName, const char *alias)
@@ -9883,9 +9895,10 @@ void setQueryCommentForNamedQuery(const char *querySetName, const char *id, cons
     setQueryCommentForNamedQuery(queryRegistry, id, queryComment);
 }
 
-const char *queryIdFromQuerySetWuid(const char *querySetName, const char *wuid, IStringVal &id)
+const char *queryIdFromQuerySetWuid(IPropertyTree *queryRegistry, const char *wuid, IStringVal &id)
 {
-    Owned<IPropertyTree> queryRegistry = getQueryRegistry(querySetName, false);
+    if (!queryRegistry)
+        return NULL;
     StringBuffer xpath;
     xpath.appendf("Query[@wuid='%s']", wuid);
     IPropertyTree *q = queryRegistry->queryPropTree(xpath.str());
@@ -9896,6 +9909,12 @@ const char *queryIdFromQuerySetWuid(const char *querySetName, const char *wuid,
     return id.str();
 }
 
+const char *queryIdFromQuerySetWuid(const char *querySetName, const char *wuid, IStringVal &id)
+{
+    Owned<IPropertyTree> queryRegistry = getQueryRegistry(querySetName, true);
+    return queryIdFromQuerySetWuid(queryRegistry, wuid, id);
+}
+
 extern WORKUNIT_API void gatherLibraryNames(StringArray &names, StringArray &unresolved, IWorkUnitFactory &workunitFactory, IConstWorkUnit &cw, IPropertyTree *queryset)
 {
     IConstWULibraryIterator &wulibraries = cw.getLibraries();

+ 4 - 1
common/workunit/workunit.hpp

@@ -1263,11 +1263,14 @@ extern WORKUNIT_API IPropertyTree * addNamedPackageSet(IPropertyTree * packageRe
 extern WORKUNIT_API void removeNamedPackage(IPropertyTree * packageRegistry, const char * id);
 extern WORKUNIT_API IPropertyTree * getPackageSetRegistry(const char * wsEclId, bool readonly);
 
-extern WORKUNIT_API void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const char *queryName, IPropertyTree *packageInfo, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid);
+extern WORKUNIT_API void addQueryToQuerySet(IWorkUnit *workunit, IPropertyTree *queryRegistry, const char *queryName, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid);
+extern WORKUNIT_API void addQueryToQuerySet(IWorkUnit *workunit, const char *querySetName, const char *queryName, WUQueryActivationOptions activateOption, StringBuffer &newQueryId, const char *userid);
+extern WORKUNIT_API void activateQuery(IPropertyTree *queryRegistry, WUQueryActivationOptions activateOption, const char *queryName, const char *queryId, const char *userid);
 extern WORKUNIT_API bool removeQuerySetAlias(const char *querySetName, const char *alias);
 extern WORKUNIT_API void addQuerySetAlias(const char *querySetName, const char *alias, const char *id);
 extern WORKUNIT_API void setSuspendQuerySetQuery(const char *querySetName, const char *id, bool suspend, const char *userid);
 extern WORKUNIT_API void deleteQuerySetQuery(const char *querySetName, const char *id);
+extern WORKUNIT_API const char *queryIdFromQuerySetWuid(IPropertyTree *queryRegistry, const char *wuid, IStringVal &id);
 extern WORKUNIT_API const char *queryIdFromQuerySetWuid(const char *querySetName, const char *wuid, IStringVal &id);
 extern WORKUNIT_API void removeQuerySetAliasesFromNamedQuery(const char *querySetName, const char * id);
 extern WORKUNIT_API void setQueryCommentForNamedQuery(const char *querySetName, const char *id, const char *comment);

+ 1 - 0
ecl/eclcmd/eclcmd_common.hpp

@@ -75,6 +75,7 @@ typedef IEclCommand *(*EclCommandFactory)(const char *cmdname);
 #define ECLOPT_ACTIVATE_S "-A"
 #define ECLOPT_ACTIVATE_INI "activateDefault"
 #define ECLOPT_ACTIVATE_ENV NULL
+#define ECLOPT_CLONE_ACTIVE_STATE "--clone-active-state"
 #define ECLOPT_SUSPEND_PREVIOUS "--suspend-prev"
 #define ECLOPT_SUSPEND_PREVIOUS_S "-sp"
 #define ECLOPT_SUSPEND_PREVIOUS_INI "suspendPrevDefault"

+ 141 - 2
ecl/eclcmd/queries/ecl-queries.cpp

@@ -482,6 +482,142 @@ private:
     bool optAllowForeign;
 };
 
+class EclCmdQueriesCopyQueryset : public EclCmdCommon
+{
+public:
+    EclCmdQueriesCopyQueryset() : optCloneActiveState(false), optAllQueries(false), optDontCopyFiles(false), optOverwrite(false), optAllowForeign(false)
+    {
+    }
+    virtual bool parseCommandLineOptions(ArgvIterator &iter)
+    {
+        if (iter.done())
+        {
+            usage();
+            return false;
+        }
+
+        for (; !iter.done(); iter.next())
+        {
+            const char *arg = iter.query();
+            if (*arg!='-')
+            {
+                if (optSourceQuerySet.isEmpty())
+                    optSourceQuerySet.set(arg);
+                else if (optDestQuerySet.isEmpty())
+                    optDestQuerySet.set(arg);
+                else
+                {
+                    fprintf(stderr, "\nunrecognized argument %s\n", arg);
+                    return false;
+                }
+                continue;
+            }
+            if (iter.matchOption(optDaliIP, ECLOPT_DALIIP))
+                continue;
+            if (iter.matchOption(optSourceProcess, ECLOPT_SOURCE_PROCESS))
+                continue;
+            if (iter.matchFlag(optCloneActiveState, ECLOPT_CLONE_ACTIVE_STATE))
+                continue;
+            if (iter.matchFlag(optDontCopyFiles, ECLOPT_DONT_COPY_FILES))
+                continue;
+            if (iter.matchFlag(optAllQueries, ECLOPT_ALL))
+                continue;
+            if (iter.matchFlag(optAllowForeign, ECLOPT_ALLOW_FOREIGN))
+                continue;
+            if (iter.matchFlag(optOverwrite, ECLOPT_OVERWRITE)||iter.matchFlag(optOverwrite, ECLOPT_OVERWRITE_S))
+                continue;
+            if (EclCmdCommon::matchCommandLineOption(iter, true)!=EclCmdOptionMatch)
+                return false;
+        }
+        return true;
+    }
+    virtual bool finalizeOptions(IProperties *globals)
+    {
+        if (!EclCmdCommon::finalizeOptions(globals))
+            return false;
+        if (optSourceQuerySet.isEmpty() || optDestQuerySet.isEmpty())
+        {
+            fputs("source and destination querysets must both be specified.\n\n", stderr);
+            return false;
+        }
+        return true;
+    }
+
+    virtual int processCMD()
+    {
+        Owned<IClientWsWorkunits> client = createCmdClient(WsWorkunits, *this);
+        Owned<IClientWUCopyQuerySetRequest> req = client->createWUCopyQuerySetRequest();
+        req->setActiveOnly(!optAllQueries);
+        req->setSource(optSourceQuerySet.get());
+        req->setTarget(optDestQuerySet.get());
+        req->setDfsServer(optDaliIP.get());
+        req->setSourceProcess(optSourceProcess);
+        req->setCloneActiveState(optCloneActiveState);
+        req->setOverwriteDfs(optOverwrite);
+        req->setCopyFiles(!optDontCopyFiles);
+        req->setAllowForeignFiles(optAllowForeign);
+
+        Owned<IClientWUCopyQuerySetResponse> resp = client->WUCopyQuerySet(req);
+        if (resp->getExceptions().ordinality())
+            outputMultiExceptions(resp->getExceptions());
+        StringArray &copied = resp->getCopiedQueries();
+        fputs("Queries copied:\n", stdout);
+        if (!copied.length())
+            fputs("  none\n\n", stdout);
+        else
+        {
+            ForEachItemIn(i, copied)
+                fprintf(stdout, "  %s\n", copied.item(i));
+            fputs("\n", stdout);
+        }
+        StringArray &existing = resp->getExistingQueries();
+        fputs("Queries already on destination target:\n", stdout);
+        if (!existing.length())
+            fputs("  none\n\n", stdout);
+        else
+        {
+            ForEachItemIn(i, existing)
+                fprintf(stdout, "  %s\n", existing.item(i));
+            fputs("\n", stdout);
+        }
+        return 0;
+    }
+    virtual void usage()
+    {
+        fputs("\nUsage:\n"
+            "\n"
+            "The 'queries copy-set' command copies a set of queries from one target to another.\n"
+            "\n"
+            "By default only active queries will be copied.  Use --all to copy all queries.\n"
+            "\n"
+            "ecl queries copy-set <source_target> <destination_target> [--clone-active-state]\n"
+            "\n"
+            " Options:\n"
+            "   <source_target>        Target cluster to copy queries from\n"
+            "   <destination_target>   Target cluster to copy queries to\n"
+            "   --all                  Copy both active and inactive queries\n"
+            "   --no-files             Do not copy files referenced by query\n"
+            "   --daliip=<ip>          Remote Dali DFS to use for copying files\n"
+            "   --source-process       Process cluster to copy files from\n"
+            "   --clone-active-state   Make copied queries active if active on source\n"
+            "   -O, --overwrite        Overwrite existing DFS file information\n"
+            "   --allow-foreign        Do not fail if foreign files are used in query (roxie)\n"
+            " Common Options:\n",
+            stdout);
+        EclCmdCommon::usage();
+    }
+private:
+    StringAttr optSourceQuerySet;
+    StringAttr optDestQuerySet;
+    StringAttr optDaliIP;
+    StringAttr optSourceProcess;
+    bool optCloneActiveState;
+    bool optOverwrite;
+    bool optDontCopyFiles;
+    bool optAllowForeign;
+    bool optAllQueries;
+};
+
 class EclCmdQueriesConfig : public EclCmdCommon
 {
 public:
@@ -633,6 +769,8 @@ IEclCommand *createEclQueriesCommand(const char *cmdname)
         return new EclCmdQueriesConfig();
     if (strieq(cmdname, "copy"))
         return new EclCmdQueriesCopy();
+    if (strieq(cmdname, "copy-set"))
+        return new EclCmdQueriesCopyQueryset();
     return NULL;
 }
 
@@ -651,9 +789,10 @@ public:
         fprintf(stdout,"\nUsage:\n\n"
             "ecl queries <command> [command options]\n\n"
             "   Queries Commands:\n"
-            "      list         list queries in queryset(s)\n"
+            "      list         list queries on target cluster(s)\n"
             "      config       update query settings\n"
-            "      copy         copy a query from one queryset to another\n"
+            "      copy         copy a query from one target cluster to another\n"
+            "      copy-set     copy queries from one target cluster to another\n"
         );
     }
 };

+ 21 - 0
esp/scm/ws_workunits.ecm

@@ -1444,6 +1444,26 @@ ESPresponse [exceptions_inline] WUQuerySetCopyQueryResponse
     string QueryId;
 };
 
+ESPrequest [nil_remove] WUCopyQuerySetRequest
+{
+    string Source;
+    string Target;
+    bool ActiveOnly(true);
+    bool CloneActiveState(true);
+    bool AllowForeignFiles(true);
+
+    string DfsServer;
+    bool CopyFiles(true);
+    bool OverwriteDfs(false);
+    string SourceProcess;
+};
+
+ESPresponse [exceptions_inline] WUCopyQuerySetResponse
+{
+    ESParray<string, QueryId> CopiedQueries;
+    ESParray<string, QueryId> ExistingQueries;
+};
+
 ESPrequest [nil_remove] WUGetZAPInfoRequest
 {
     string WUID;
@@ -1541,6 +1561,7 @@ ESPservice [
     ESPmethod WUQuerysetQueryAction(WUQuerySetQueryActionRequest, WUQuerySetQueryActionResponse);
     ESPmethod WUQuerysetAliasAction(WUQuerySetAliasActionRequest, WUQuerySetAliasActionResponse);
     ESPmethod WUQuerysetCopyQuery(WUQuerySetCopyQueryRequest, WUQuerySetCopyQueryResponse);
+    ESPmethod WUCopyQuerySet(WUCopyQuerySetRequest, WUCopyQuerySetResponse);
     ESPmethod [resp_xsl_default("/esp/xslt/WUCopyLogicalFiles.xslt")] WUCopyLogicalFiles(WUCopyLogicalFilesRequest, WUCopyLogicalFilesResponse);
     ESPmethod WUQueryConfig(WUQueryConfigRequest, WUQueryConfigResponse);
     ESPmethod WUListQueries(WUListQueriesRequest, WUListQueriesResponse);

+ 166 - 2
esp/services/ws_workunits/ws_workunitsQuerySets.cpp

@@ -754,7 +754,7 @@ bool CWsWorkunitsEx::onWUPublishWorkunit(IEspContext &context, IEspWUPublishWork
 
     StringBuffer queryId;
     WUQueryActivationOptions activate = (WUQueryActivationOptions)req.getActivate();
-    addQueryToQuerySet(wu, target.str(), queryName.str(), NULL, activate, queryId, context.queryUserId());
+    addQueryToQuerySet(wu, target.str(), queryName.str(), activate, queryId, context.queryUserId());
     if (req.getMemoryLimit() || !req.getTimeLimit_isNull() || !req.getWarnTimeLimit_isNull() || req.getPriority() || req.getComment())
     {
         Owned<IPropertyTree> queryTree = getQueryById(target.str(), queryId, false);
@@ -1797,6 +1797,170 @@ bool splitQueryPath(const char *path, StringBuffer &netAddress, StringBuffer &qu
     return true;
 }
 
+class QueryCloner
+{
+public:
+    QueryCloner(IEspContext *_context, const char *source, const char *_target) :
+        context(_context), cloneFilesEnabled(false), target(_target), overwriteDfs(false)
+    {
+        srcQuerySet.setown(getQueryRegistry(source, true));
+        if (!srcQuerySet)
+            throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "Source Queryset %s not found", source);
+
+        destQuerySet.setown(getQueryRegistry(target, false));
+        if (!destQuerySet) // getQueryRegistry should have created if not found
+            throw MakeStringException(ECLWATCH_QUERYSET_NOT_FOUND, "Destination Queryset %s could not be created, or found", target.sget());
+
+        factory.setown(getWorkUnitFactory(context->querySecManager(), context->queryUser()));
+    }
+    void clone(const char *name, const char *id, bool makeActive)
+    {
+        Owned<IPropertyTree> query = getQueryById(srcQuerySet, id);
+        if (!query)
+            return;
+        const char *wuid = query->queryProp("@wuid");
+        if (!wuid || !*wuid)
+            return;
+        SCMStringBuffer existingQueryId;
+        queryIdFromQuerySetWuid(destQuerySet, wuid, existingQueryId);
+        if (existingQueryId.length())
+        {
+            existingQueryIds.append(existingQueryId.str());
+            if (makeActive)
+                activateQuery(destQuerySet, ACTIVATE_SUSPEND_PREVIOUS, name, existingQueryId.str(), context->queryUserId());
+            return;
+        }
+        StringBuffer newQueryId;
+        Owned<IWorkUnit> workunit = factory->updateWorkUnit(wuid);
+        addQueryToQuerySet(workunit, destQuerySet, name, makeActive ? ACTIVATE_SUSPEND_PREVIOUS : DO_NOT_ACTIVATE, newQueryId, context->queryUserId());
+        copiedQueryIds.append(newQueryId);
+        Owned<IPropertyTree> destQuery = getQueryById(destQuerySet, newQueryId);
+        if (destQuery)
+        {
+            Owned<IAttributeIterator> aiter = query->getAttributes();
+            ForEach(*aiter)
+            {
+                const char *atname = aiter->queryName();
+                if (!destQuery->hasProp(atname))
+                    destQuery->setProp(atname, aiter->queryValue());
+            }
+            if (cloneFilesEnabled && wufiles)
+                wufiles->addFilesFromQuery(workunit, pm, newQueryId);
+        }
+    }
+
+    void cloneActive(bool makeActive)
+    {
+        Owned<IPropertyTreeIterator> activeQueries = srcQuerySet->getElements("Alias");
+        ForEach(*activeQueries)
+        {
+            IPropertyTree &alias = activeQueries->query();
+            const char *name = alias.queryProp("@name");
+            const char *id = alias.queryProp("@id");
+            clone(name, id, makeActive);
+        }
+    }
+
+    void cloneAll(bool cloneActiveState)
+    {
+        Owned<IPropertyTreeIterator> allQueries = srcQuerySet->getElements("Query");
+        ForEach(*allQueries)
+        {
+            IPropertyTree &query = allQueries->query();
+            const char *name = query.queryProp("@name");
+            const char *id = query.queryProp("@id");
+            bool makeActive = false;
+            if (cloneActiveState)
+            {
+                VStringBuffer xpath("Alias[@id='%s']", id);
+                makeActive = srcQuerySet->hasProp(xpath);
+            }
+            clone(name, id, makeActive);
+        }
+    }
+    void enableFileCloning(const char *dfsServer, const char *destProcess, const char *sourceProcess, bool _overwriteDfs, bool allowForeign)
+    {
+        cloneFilesEnabled = true;
+        overwriteDfs = _overwriteDfs;
+        splitDerivedDfsLocation(dfsServer, srcCluster, dfsIP, srcPrefix, sourceProcess, sourceProcess, NULL, NULL);
+        wufiles.setown(createReferencedFileList(context->queryUserId(), context->queryPassword(), allowForeign));
+        Owned<IHpccPackageSet> ps = createPackageSet(destProcess);
+        pm.set(ps->queryActiveMap(target));
+        process.set(destProcess);
+    }
+
+    void cloneFiles()
+    {
+        if (cloneFilesEnabled)
+        {
+            wufiles->resolveFiles(process, dfsIP, srcPrefix, srcCluster, !overwriteDfs, true);
+            Owned<IDFUhelper> helper = createIDFUhelper();
+            wufiles->cloneAllInfo(helper, overwriteDfs, true);
+        }
+    }
+private:
+    Linked<IEspContext> context;
+    Linked<IWorkUnitFactory> factory;
+    Owned<IPropertyTree> destQuerySet;
+    Owned<IPropertyTree> srcQuerySet;
+    Owned<IReferencedFileList> wufiles;
+    Owned<const IHpccPackageMap> pm;
+    StringBuffer dfsIP;
+    StringBuffer srcCluster;
+    StringBuffer srcPrefix;
+    StringAttr target;
+    StringAttr process;
+    bool cloneFilesEnabled;
+    bool overwriteDfs;
+
+public:
+    StringArray existingQueryIds;
+    StringArray copiedQueryIds;
+};
+
+
+bool CWsWorkunitsEx::onWUCopyQuerySet(IEspContext &context, IEspWUCopyQuerySetRequest &req, IEspWUCopyQuerySetResponse &resp)
+{
+    const char *source = req.getSource();
+    if (!source || !*source)
+        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "No source target specified");
+    if (!isValidCluster(source))
+        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid source target name: %s", source);
+
+    const char *target = req.getTarget();
+    if (!target || !*target)
+        throw MakeStringException(ECLWATCH_MISSING_PARAMS, "No destination target specified");
+    if (!isValidCluster(target))
+        throw MakeStringException(ECLWATCH_INVALID_CLUSTER_NAME, "Invalid destination target name: %s", target);
+
+    QueryCloner cloner(&context, source, target);
+
+    SCMStringBuffer process;
+    if (req.getCopyFiles())
+    {
+        Owned <IConstWUClusterInfo> clusterInfo = getTargetClusterInfo(target);
+        if (clusterInfo && clusterInfo->getPlatform()==RoxieCluster)
+        {
+            clusterInfo->getRoxieProcess(process);
+            if (!process.length())
+                throw MakeStringException(ECLWATCH_INVALID_CLUSTER_INFO, "DFS process cluster not found for destination target %s", target);
+            cloner.enableFileCloning(req.getDfsServer(), process.str(), req.getSourceProcess(), req.getOverwriteDfs(), req.getAllowForeignFiles());
+        }
+    }
+
+    if (req.getActiveOnly())
+        cloner.cloneActive(req.getCloneActiveState());
+    else
+        cloner.cloneAll(req.getCloneActiveState());
+
+    cloner.cloneFiles();
+
+    resp.setCopiedQueries(cloner.copiedQueryIds);
+    resp.setExistingQueries(cloner.existingQueryIds);
+
+    return true;
+}
+
 bool CWsWorkunitsEx::onWUQuerysetCopyQuery(IEspContext &context, IEspWUQuerySetCopyQueryRequest &req, IEspWUQuerySetCopyQueryResponse &resp)
 {
     unsigned start = msTick();
@@ -1865,7 +2029,7 @@ bool CWsWorkunitsEx::onWUQuerysetCopyQuery(IEspContext &context, IEspWUQuerySetC
 
     StringBuffer targetQueryId;
     WUQueryActivationOptions activate = (WUQueryActivationOptions)req.getActivate();
-    addQueryToQuerySet(wu, target, queryName.str(), NULL, activate, targetQueryId, context.queryUserId());
+    addQueryToQuerySet(wu, target, queryName.str(), activate, targetQueryId, context.queryUserId());
 
     Owned<IPropertyTree> queryTree = getQueryById(target, targetQueryId, false);
     if (queryTree)

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

@@ -189,6 +189,7 @@ public:
     bool onWUQuerysetAliasAction(IEspContext &context, IEspWUQuerySetAliasActionRequest &req, IEspWUQuerySetAliasActionResponse &resp);
     bool onWUQueryConfig(IEspContext &context, IEspWUQueryConfigRequest &req, IEspWUQueryConfigResponse &resp);
     bool onWUQuerysetCopyQuery(IEspContext &context, IEspWUQuerySetCopyQueryRequest &req, IEspWUQuerySetCopyQueryResponse &resp);
+    bool onWUCopyQuerySet(IEspContext &context, IEspWUCopyQuerySetRequest &req, IEspWUCopyQuerySetResponse &resp);
     bool onWUCopyLogicalFiles(IEspContext &context, IEspWUCopyLogicalFilesRequest &req, IEspWUCopyLogicalFilesResponse &resp);
     bool onWUQueryDetails(IEspContext &context, IEspWUQueryDetailsRequest & req, IEspWUQueryDetailsResponse & resp);
     bool onWUListQueries(IEspContext &context, IEspWUListQueriesRequest &req, IEspWUListQueriesResponse &resp);