Pārlūkot izejas kodu

Add ecl command line support for running queries

Add new ecl command "run" which can execute from an ecl file, archive, dll,
wuid, or published query.

basic usage:

ecl run [--cluster=<c>][--input=<file|xml>][--wait=<ms>] <wuid>
ecl run [--cluster=<c>][--input=<file|xml>][--wait=<ms>] <queryset> <query>
ecl run [--cluster=<c>][--name=<nm>][--input=<file|xml>][--wait=<i>] <dll|->
ecl run --cluster=<c> --name=<nm> [--input=<file|xml>][--wait=<i>] <archive|->
ecl run --cluster=<c> --name=<nm> [--input=<file|xml>][--wait=<i>] <eclfile|->

Signed-off-by: Anthony Fishbeck <Anthony.Fishbeck@lexisnexis.com>
Anthony Fishbeck 13 gadi atpakaļ
vecāks
revīzija
ed1b9c0fd4

+ 161 - 44
common/wuwebview/wuwebview.cpp

@@ -19,6 +19,7 @@
 #include "jlib.hpp"
 #include "jexcept.hpp"
 #include "jptree.hpp"
+#include "junicode.hpp"
 #include "workunit.hpp"
 #include "dllserver.hpp"
 #include "thorplugin.hpp"
@@ -96,16 +97,16 @@ public:
         buffer.append(value);
     }
 
-    void appendSchemaResource(IPropertyTree &res, ILoadedDllEntry &loadedDll)
+    void appendSchemaResource(IPropertyTree &res, ILoadedDllEntry *dll)
     {
-        if (flags & WWV_OMIT_SCHEMAS)
+        if (!dll || (flags & WWV_OMIT_SCHEMAS))
             return;
         if (res.getPropInt("@seq", -1)>=0 && res.hasProp("@id"))
         {
             int id = res.getPropInt("@id");
             size32_t len = 0;
             const void *data = NULL;
-            if (loadedDll.getResource(len, data, "RESULT_XSD", (unsigned) id) && len>0)
+            if (dll->getResource(len, data, "RESULT_XSD", (unsigned) id) && len>0)
             {
                 buffer.append("<XmlSchema name=\"").append(res.queryProp("@name")).append("\">");
                 if (res.getPropBool("@compressed"))
@@ -125,21 +126,25 @@ public:
         }
     }
 
-    void appendManifestSchemas(IPropertyTree &manifest, ILoadedDllEntry &loadedDll)
+    void appendManifestSchemas(IPropertyTree &manifest, ILoadedDllEntry *dll)
     {
         assertex(!finalized);
+        if (!dll)
+            return;
         Owned<IPropertyTreeIterator> iter = manifest.getElements("Resource[@type='RESULT_XSD']");
         ForEach(*iter)
-            appendSchemaResource(iter->query(), loadedDll);
+            appendSchemaResource(iter->query(), dll);
     }
 
-    void appendManifestResultSchema(IPropertyTree &manifest, const char *resultname, ILoadedDllEntry &loadedDll)
+    void appendManifestResultSchema(IPropertyTree &manifest, const char *resultname, ILoadedDllEntry *dll)
     {
         assertex(!finalized);
+        if (!dll)
+            return;
         VStringBuffer xpath("Resource[@name='%s'][@type='RESULT_XSD']", resultname);
         IPropertyTree *res=manifest.queryPropTree(xpath.str());
         if (res)
-            appendSchemaResource(*res, loadedDll);
+            appendSchemaResource(*res, dll);
     }
 
     void appendXML(IPropertyTree *xml, const char *tag=NULL)
@@ -258,22 +263,24 @@ class WuWebView : public CInterface,
 public:
     IMPLEMENT_IINTERFACE;
 
-    WuWebView(IConstWorkUnit &wu, const char *queryname, const char *wdir, bool mapEspDir) :
-        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir)
+    WuWebView(IConstWorkUnit &wu, const char *queryname, const char *wdir, bool mapEspDir, bool delay=true) :
+        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir), delayedDll(delay)
     {
         name.set(queryname);
-        load(wu);
+        setWorkunit(wu);
     }
 
-    WuWebView(const char *wuid, const char *queryname, const char *wdir, bool mapEspDir) :
-        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir)
+    WuWebView(const char *wuid, const char *queryname, const char *wdir, bool mapEspDir, bool delay=true) :
+        manifestIncludePathsSet(false), dir(wdir), mapEspDirectories(mapEspDir), delayedDll(delay)
     {
         name.set(queryname);
-        load(wuid);
+        setWorkunit(wuid);
     }
 
-    void load(IConstWorkUnit &wu);
-    void load(const char *wuid);
+    void setWorkunit(IConstWorkUnit &wu);
+    void setWorkunit(const char *wuid);
+    ILoadedDllEntry *loadDll(bool force=false);
+
     IPropertyTree *ensureManifest();
 
     virtual void getResultViewNames(StringArray &names);
@@ -296,10 +303,16 @@ public:
     void calculateResourceIncludePaths();
     virtual bool getInclude(const char *includename, MemoryBuffer &includebuf, bool &pathOnly);
 
+    void addVariableFromPTree(IWorkUnit *w, IConstWUResult &vardef, IResultSetMetaData &metadef, const char *varname, IPropertyTree *valtree);
+    void addInputsFromPTree(IPropertyTree *pt);
+    void addInputsFromXml(const char *xml);
+
+
 protected:
     SCMStringBuffer dllname;
-    Owned<IConstWorkUnit> wu;
-    Owned<ILoadedDllEntry> loadedDll;
+    Owned<IConstWorkUnit> cw;
+    Owned<ILoadedDllEntry> dll;
+    bool delayedDll;
     Owned<IPropertyTree> manifest;
     SCMStringBuffer name;
     bool mapEspDirectories;
@@ -314,7 +327,7 @@ IPropertyTree *WuWebView::ensureManifest()
     if (!manifest)
     {
         StringBuffer xml;
-        manifest.setown((loadedDll && getEmbeddedManifestXML(loadedDll, xml)) ? createPTreeFromXMLString(xml.str()) : createPTree());
+        manifest.setown((loadDll() && getEmbeddedManifestXML(dll, xml)) ? createPTreeFromXMLString(xml.str()) : createPTree());
     }
     return manifest.get();
 }
@@ -395,14 +408,14 @@ void WuWebView::getResultViewNames(StringArray &names)
 
 void WuWebView::getResource(IPropertyTree *res, StringBuffer &content)
 {
-    if (!loadedDll)
+    if (!loadDll())
         return;
     if (res->hasProp("@id"))
     {
         int id = res->getPropInt("@id");
         size32_t len = 0;
         const void *data = NULL;
-        if (loadedDll->getResource(len, data, res->queryProp("@type"), (unsigned) id) && len>0)
+        if (dll->getResource(len, data, res->queryProp("@type"), (unsigned) id) && len>0)
         {
             if (res->getPropBool("@compressed"))
             {
@@ -453,8 +466,7 @@ void WuWebView::renderExpandedResults(const char *viewName, WuExpandedResultBuff
     if (!view)
         throw MakeStringException(WUWEBERR_ViewResourceNotFound, "Result view %s not found", viewName);
     expanded.appendXML(view, "view");
-    if (loadedDll)
-        expanded.appendManifestSchemas(*mf, *loadedDll);
+    expanded.appendManifestSchemas(*mf, loadDll());
     expanded.finalize();
     const char *type=view->queryProp("@type");
     if (!type)
@@ -491,14 +503,14 @@ void WuWebView::renderResults(const char *viewName, const char *xml, StringBuffe
 void WuWebView::renderResults(const char *viewName, StringBuffer &out)
 {
     WuExpandedResultBuffer buffer(name.str(), WWV_ADD_RESPONSE_TAG | WWV_ADD_RESULTS_TAG);
-    buffer.appendResults(wu, username.get(), pw.get());
+    buffer.appendResults(cw, username.get(), pw.get());
     renderExpandedResults(viewName, buffer, out);
 }
 
 void WuWebView::renderSingleResult(const char *viewName, const char *resultname, StringBuffer &out)
 {
     WuExpandedResultBuffer buffer(name.str(), WWV_ADD_RESPONSE_TAG | WWV_ADD_RESULTS_TAG);
-    buffer.appendSingleResult(wu, resultname, username.get(), pw.get());
+    buffer.appendSingleResult(cw, resultname, username.get(), pw.get());
     renderExpandedResults(viewName, buffer, out);
 }
 
@@ -506,8 +518,7 @@ void WuWebView::expandResults(const char *xml, StringBuffer &out, unsigned flags
 {
     WuExpandedResultBuffer expander(name.str(), flags);
     expander.appendDatasetsFromXML(xml);
-    if (loadedDll)
-        expander.appendManifestSchemas(*ensureManifest(), *loadedDll);
+    expander.appendManifestSchemas(*ensureManifest(), loadDll());
     expander.finalize();
     out.append(expander.buffer);
 }
@@ -515,7 +526,7 @@ void WuWebView::expandResults(const char *xml, StringBuffer &out, unsigned flags
 void WuWebView::expandResults(StringBuffer &out, unsigned flags)
 {
     SCMStringBuffer xml;
-    getFullWorkUnitResultsXML(username.get(), pw.get(), wu, xml, false, ExceptionSeverityInformation);
+    getFullWorkUnitResultsXML(username.get(), pw.get(), cw, xml, false, ExceptionSeverityInformation);
     expandResults(xml.str(), out, flags);
 }
 
@@ -523,8 +534,7 @@ void WuWebView::applyResultsXSLT(const char *filename, const char *xml, StringBu
 {
     WuExpandedResultBuffer buffer(name.str(), WWV_ADD_RESPONSE_TAG | WWV_ADD_RESULTS_TAG);
     buffer.appendDatasetsFromXML(xml);
-    if (loadedDll)
-        buffer.appendManifestSchemas(*ensureManifest(), *loadedDll);
+    buffer.appendManifestSchemas(*ensureManifest(), loadDll());
 
     Owned<IXslTransform> t = getXslProcessor()->createXslTransform();
     t->setIncludeHandler(this);
@@ -540,37 +550,144 @@ void WuWebView::applyResultsXSLT(const char *filename, const char *xml, StringBu
 void WuWebView::applyResultsXSLT(const char *filename, StringBuffer &out)
 {
     SCMStringBuffer xml;
-    getFullWorkUnitResultsXML(username.get(), pw.get(), wu, xml, false, ExceptionSeverityInformation);
+    getFullWorkUnitResultsXML(username.get(), pw.get(), cw, xml, false, ExceptionSeverityInformation);
     applyResultsXSLT(filename, xml.str(), out);
 }
 
-void WuWebView::load(IConstWorkUnit &cwu)
+ILoadedDllEntry *WuWebView::loadDll(bool force)
+{
+    if (!dll && (force || delayedDll))
+    {
+        try
+        {
+            dll.setown(queryDllServer().loadDll(dllname.str(), DllLocationAnywhere));
+        }
+        catch(...)
+        {
+            DBGLOG("Failed to load %s", dllname.str());
+        }
+        delayedDll=false;
+    }
+    return dll.get();
+}
+void WuWebView::setWorkunit(IConstWorkUnit &_cw)
 {
-    wu.set(&cwu);
+    cw.set(&_cw);
     if (!name.length())
     {
-        wu->getJobName(name);
+        cw->getJobName(name);
         name.s.replace(' ','_');
     }
-    Owned<IConstWUQuery> q = wu->getQuery();
+    Owned<IConstWUQuery> q = cw->getQuery();
     q->getQueryDllName(dllname);
-    try
-    {
-        loadedDll.setown(queryDllServer().loadDll(dllname.str(), DllLocationAnywhere));
-    }
-    catch(...)
-    {
-        DBGLOG("Failed to load %s", dllname.str());
-    }
+    if (!delayedDll)
+        loadDll(true);
 }
 
-void WuWebView::load(const char *wuid)
+void WuWebView::setWorkunit(const char *wuid)
 {
     Owned<IWorkUnitFactory> factory = getWorkUnitFactory();
     Owned<IConstWorkUnit> wu = factory->openWorkUnit(wuid, false);
     if (!wu)
         throw MakeStringException(WUWEBERR_WorkUnitNotFound, "Workunit not found %s", wuid);
-    load(*wu);
+    setWorkunit(*wu);
+}
+
+void WuWebView::addVariableFromPTree(IWorkUnit *w, IConstWUResult &vardef, IResultSetMetaData &metadef, const char *varname, IPropertyTree *valtree)
+{
+    if (!varname || !*varname)
+        return;
+
+    Owned<IWUResult> var = w->updateVariableByName(varname);
+    if (!vardef.isResultScalar())
+    {
+        StringBuffer ds;
+        if (valtree->hasChildren())
+            toXML(valtree, ds);
+        else
+        {
+            const char *val = valtree->queryProp(NULL);
+            if (val)
+                decodeXML(val, ds);
+        }
+        if (ds.length())
+            var->setResultRaw(ds.length(), ds.str(), ResultFormatXml);
+    }
+    else
+    {
+        const char *val = valtree->queryProp(NULL);
+        if (val && *val)
+        {
+            switch (metadef.getColumnDisplayType(0))
+            {
+                case TypeBoolean:
+                    var->setResultBool(strieq(val, "1") || strieq(val, "true") || strieq(val, "on"));
+                    break;
+                case TypeInteger:
+                    var->setResultInt(_atoi64(val));
+                    break;
+                case TypeUnsignedInteger:
+                    var->setResultInt(_atoi64(val));
+                    break;
+                case TypeReal:
+                    var->setResultReal(atof(val));
+                    break;
+                case TypeSet:
+                case TypeDataset:
+                case TypeData:
+                    var->setResultRaw(strlen(val), val, ResultFormatRaw);
+                    break;
+                case TypeUnicode: {
+                    MemoryBuffer target;
+                    convertUtf(target, UtfReader::Utf16le, strlen(val), val, UtfReader::Utf8);
+                    var->setResultUnicode(target.toByteArray(), (target.length()>1) ? target.length()/2 : 0);
+                    }
+                    break;
+                case TypeString:
+                case TypeUnknown:
+                default:
+                    var->setResultString(val, strlen(val));
+                    break;
+                    break;
+            }
+
+            var->setResultStatus(ResultStatusSupplied);
+        }
+    }
+}
+
+void WuWebView::addInputsFromPTree(IPropertyTree *pt)
+{
+    IPropertyTree *start = pt;
+    if (start->hasProp("Envelope"))
+        start=start->queryPropTree("Envelope");
+    if (start->hasProp("Body"))
+        start=start->queryPropTree("Body/*[1]");
+
+    Owned<IResultSetFactory> resultSetFactory(getResultSetFactory(username.get(), pw.get()));
+    Owned<IPropertyTreeIterator> it = start->getElements("*");
+
+    WorkunitUpdate wu(&cw->lock());
+
+    ForEach(*it)
+    {
+        IPropertyTree &eclparm=it->query();
+        const char *varname = eclparm.queryName();
+
+        IConstWUResult *vardef = wu->getVariableByName(varname);
+        if (vardef)
+        {
+            Owned<IResultSetMetaData> metadef = resultSetFactory->createResultSetMeta(vardef);
+            if (metadef)
+                addVariableFromPTree(wu.get(), *vardef, *metadef, varname, &eclparm);
+        }
+    }
+}
+
+void WuWebView::addInputsFromXml(const char *xml)
+{
+    Owned<IPropertyTree> pt = createPTreeFromXMLString(xml, ipt_none, (XmlReaderOptions)(xr_ignoreWhiteSpace|xr_ignoreNameSpaces));
+    addInputsFromPTree(pt.get());
 }
 
 extern WUWEBVIEW_API IWuWebView *createWuWebView(IConstWorkUnit &wu, const char *queryname, const char *dir, bool mapEspDirectories)

+ 2 - 0
common/wuwebview/wuwebview.hpp

@@ -51,6 +51,8 @@ interface IWuWebView : extends IInterface
     virtual StringBuffer &aggregateResources(const char *type, StringBuffer &content)=0;
     virtual void expandResults(const char *xml, StringBuffer &out, unsigned flags)=0;
     virtual void expandResults(StringBuffer &out, unsigned flags)=0;
+    virtual void addInputsFromPTree(IPropertyTree *pt)=0;
+    virtual void addInputsFromXml(const char *xml)=0;
 };
 
 extern WUWEBVIEW_API IWuWebView *createWuWebView(IConstWorkUnit &wu, const char *queryname, const char*dir, bool mapEspDir);

+ 8 - 1
ecl/eclcmd/eclcmd_common.cpp

@@ -286,7 +286,14 @@ eclCmdOptionMatchIndicator EclCmdWithEclTarget::matchCommandLineOption(ArgvItera
     {
         if (optObj.value.length())
         {
-            fprintf(stderr, "\nmultiple targets (%s and %s) not currently supported\n", optObj.value.sget(), arg);
+            if (optObj.type==eclObjTypeUnknown && (optObj.accept & eclObjQuery) && !optObj.query.length())
+            {
+                optObj.type=eclObjQuery;
+                optObj.query.set(arg);
+                return EclCmdOptionMatch;
+            }
+
+            fprintf(stderr, "\nunrecognized argument %s\n", arg);
             return EclCmdOptionCompletion;
         }
         optObj.set(arg);

+ 9 - 1
ecl/eclcmd/eclcmd_common.hpp

@@ -53,6 +53,12 @@ typedef IEclCommand *(*EclCommandFactory)(const char *cmdname);
 #define ECLOPT_ACTIVATE_INI "activateDefault"
 #define ECLOPT_ACTIVATE_ENV NULL
 
+#define ECLOPT_WAIT "--wait"
+#define ECLOPT_WAIT_INI "waitTimeout"
+#define ECLOPT_WAIT_ENV "ECL_WAIT_TIMEOUT"
+
+#define ECLOPT_INPUT "--input"
+
 #define ECLOPT_WUID "--wuid"
 #define ECLOPT_CLUSTER "--cluster"
 #define ECLOPT_NAME "--name"
@@ -79,7 +85,8 @@ enum eclObjParameterType
     eclObjSource = 0x01,
     eclObjArchive = 0x02,
     eclObjSharedObject = 0x04,
-    eclObjWuid = 0x08
+    eclObjWuid = 0x08,
+    eclObjQuery = 0x10
 };
 
 #define eclObjSourceOrArchive (eclObjSource|eclObjArchive)
@@ -103,6 +110,7 @@ public:
 public:
     eclObjParameterType type;
     StringAttr value;
+    StringAttr query;
     MemoryBuffer mb;
     unsigned accept;
 };

+ 167 - 2
ecl/eclcmd/eclcmd_core.cpp

@@ -154,7 +154,7 @@ private:
 
 
 
-bool doDeploy(EclCmdWithEclTarget &cmd, IClientWsWorkunits *client, const char *cluster, const char *name, StringBuffer *wuid)
+bool doDeploy(EclCmdWithEclTarget &cmd, IClientWsWorkunits *client, const char *cluster, const char *name, StringBuffer *wuid, bool displayWuid=true)
 {
     StringBuffer s;
     if (cmd.optVerbose)
@@ -203,7 +203,8 @@ bool doDeploy(EclCmdWithEclTarget &cmd, IClientWsWorkunits *client, const char *
         fprintf(stdout, "\n");
         if (cmd.optVerbose)
             fprintf(stdout, "Deployed\n   wuid: ");
-        fprintf(stdout, "%s\n", w);
+        if (displayWuid || cmd.optVerbose)
+            fprintf(stdout, "%s\n", w);
         const char *state = resp->getWorkunit().getState();
         if (cmd.optVerbose)
             fprintf(stdout, "   state: %s\n", state);
@@ -427,6 +428,168 @@ private:
     bool activateSet;
 };
 
+class EclCmdRun : public EclCmdWithEclTarget
+{
+public:
+    EclCmdRun() : optWaitTime((unsigned)-1)
+    {
+        optObj.accept = eclObjWuid | eclObjArchive | eclObjSharedObject | eclObjWuid | eclObjQuery;
+    }
+    virtual bool parseCommandLineOptions(ArgvIterator &iter)
+    {
+        if (iter.done())
+        {
+            usage();
+            return false;
+        }
+
+        for (; !iter.done(); iter.next())
+        {
+            if (iter.matchOption(optObj.value, ECLOPT_WUID))
+                continue;
+            if (iter.matchOption(optName, ECLOPT_NAME))
+                continue;
+            if (iter.matchOption(optCluster, ECLOPT_CLUSTER))
+                continue;
+            if (iter.matchOption(optInput, ECLOPT_INPUT))
+                continue;
+            if (iter.matchOption(optWaitTime, ECLOPT_WAIT))
+                continue;
+            if (EclCmdWithEclTarget::matchCommandLineOption(iter, true)!=EclCmdOptionMatch)
+                return false;
+        }
+        return true;
+    }
+    virtual bool finalizeOptions(IProperties *globals)
+    {
+        if (!EclCmdWithEclTarget::finalizeOptions(globals))
+            return false;
+        if (optObj.value.isEmpty())
+        {
+            fprintf(stderr, "\nMust specify a Query, WUID, ECL File, Archive, or shared object to run\n");
+            return false;
+        }
+        if (optObj.type==eclObjTypeUnknown)
+        {
+            fprintf(stderr, "\nCan't determine content type of argument %s\n", optObj.value.sget());
+            return false;
+        }
+        if (optObj.type==eclObjArchive || optObj.type==eclObjSource)
+        {
+            if (optCluster.isEmpty())
+            {
+                fprintf(stderr, "\nCluster must be specified when publishing ECL Text or Archive\n");
+                return false;
+            }
+            if (optName.isEmpty())
+            {
+                fprintf(stderr, "\nQuery name must be specified when publishing an ECL Text or Archive\n");
+                return false;
+            }
+        }
+        if (optInput.length())
+        {
+            const char *in = optInput.get();
+            while (*in && isspace(*in)) in++;
+            if (*in!='<')
+            {
+                StringBuffer content;
+                content.loadFile(in);
+                optInput.set(content.str());
+            }
+        }
+        return true;
+    }
+    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<IClientWURunRequest> req = client->createWURunRequest();
+        req->setCloneWorkunit(true);
+
+        StringBuffer wuid;
+        StringBuffer queryset;
+        StringBuffer query;
+
+        if (optObj.type==eclObjWuid)
+        {
+            req->setWuid(wuid.set(optObj.value.get()).str());
+            if (optVerbose)
+                fprintf(stdout, "Running workunit %s\n", wuid.str());
+        }
+        else if (optObj.type==eclObjQuery)
+        {
+            req->setQuerySet(queryset.set(optObj.value.get()).str());
+            req->setQuery(query.set(optObj.query.get()).str());
+            if (optVerbose)
+                fprintf(stdout, "Running query %s/%s\n", queryset.str(), query.str());
+        }
+        else
+        {
+            req->setCloneWorkunit(false);
+            if (!doDeploy(*this, client, optCluster.get(), optName.get(), &wuid, optVerbose))
+                return 1;
+            req->setWuid(wuid.str());
+            if (optVerbose)
+                fprintf(stdout, "Running deployed workunit %s\n", wuid.str());
+        }
+
+        if (optCluster.length())
+            req->setCluster(optCluster.get());
+        req->setWait((int)optWaitTime);
+        if (optInput.length())
+            req->setInput(optInput.get());
+
+        Owned<IClientWURunResponse> resp = client->WURun(req);
+        if (resp->getExceptions().ordinality())
+            outputMultiExceptions(resp->getExceptions());
+
+        StringBuffer respwuid(resp->getWuid());
+        if (optVerbose && respwuid.length() && !streq(wuid.str(), respwuid.str()))
+            fprintf(stderr, "As %s\n", resp->getWuid());
+        if (!streq(resp->getState(), "completed"))
+        {
+            fprintf(stderr, "%s\n", resp->getState());
+            return 1;
+        }
+        if (resp->getResults())
+            fprintf(stdout, "%s", resp->getResults());
+
+        return 0;
+    }
+    virtual void usage()
+    {
+        fprintf(stdout,"\nUsage:\n\n"
+            "ecl run [--cluster=<c>][--input=<file|xml>][--wait=<ms>] <wuid>\n"
+            "ecl run [--cluster=<c>][--input=<file|xml>][--wait=<ms>] <queryset> <query>\n"
+            "ecl run [--cluster=<c>][--name=<nm>][--input=<file|xml>][--wait=<i>] <dll|->\n"
+            "ecl run --cluster=<c> --name=<nm> [--input=<file|xml>][--wait=<i>] <archive|->\n"
+            "ecl run --cluster=<c> --name=<nm> [--input=<file|xml>][--wait=<i>] <eclfile|->\n\n"
+            "      -                    specifies object should be read from stdin\n"
+            "      <wuid>               workunit to publish\n"
+            "      <archive|->          archive to publish\n"
+            "      <ecl_file|->         ECL text file to publish\n"
+            "      <so|dll|->           workunit dll or shared object to publish\n"
+            "   Options:\n"
+            "      --cluster=<cluster>  cluster to run job on\n"
+            "                           (defaults to cluster defined inside workunit)\n"
+            "      --name=<name>        job name\n"
+            "      --input=<file|xml>   file or xml content to use as query input\n"
+            "      --wait=<ms>          time to wait for completion\n"
+        );
+        EclCmdWithEclTarget::usage();
+    }
+private:
+    StringAttr optCluster;
+    StringAttr optName;
+    StringAttr optInput;
+    unsigned optWaitTime;
+};
+
 class EclCmdActivate : public EclCmdCommon
 {
 public:
@@ -637,6 +800,8 @@ IEclCommand *createCoreEclCommand(const char *cmdname)
         return new EclCmdDeploy();
     if (strieq(cmdname, "publish"))
         return new EclCmdPublish();
+    if (strieq(cmdname, "run"))
+        return new EclCmdRun();
     if (strieq(cmdname, "activate"))
         return new EclCmdActivate();
     if (strieq(cmdname, "deactivate"))

+ 3 - 3
ecl/eclcmd/eclcmd_shell.cpp

@@ -192,9 +192,9 @@ void EclCMDShell::usage()
     fprintf(stdout,"\nUsage:\n"
         "    ecl [--version] <command> [<args>]\n\n"
            "Commonly used commands:\n"
-           "   deploy      create an HPCC workunit from a local ecl file, archive,\n"
-           "               or shared object\n"
-           "   publish     add an HPCC workunit to a query set\n"
+           "   deploy      create a workunit from an ecl file, archive, or dll\n"
+           "   publish     add a workunit to a query set\n"
+           "   run         run the given ecl file, archive, dll, wuid, or query\n"
            "   activate    activate a published query\n"
            "   deactivate  deactivate the given query alias name\n"
            "\nRun 'ecl help <command>' for more information on a specific command\n\n"

+ 19 - 0
esp/scm/ws_workunits.ecm

@@ -481,6 +481,24 @@ ESPresponse [exceptions_inline] WUResubmitResponse
 {
 };
 
+ESPrequest WURunRequest
+{
+    string QuerySet;
+    string Query;
+    string Wuid;
+    bool CloneWorkunit;
+    string Cluster;
+    int Wait(-1);
+    [rows(15)] string Input;
+};
+
+ESPresponse [exceptions_inline] WURunResponse
+{
+    string Wuid;
+    string State;
+    string Results;
+};
+
 ESPrequest WUSubmitRequest
 {
     string Wuid;
@@ -1196,6 +1214,7 @@ ESPservice [
     ESPmethod WUAbort(WUAbortRequest, WUAbortResponse);
     ESPmethod WUProtect(WUProtectRequest, WUProtectResponse);
     ESPmethod WUResubmit(WUResubmitRequest, WUResubmitResponse); //????
+    ESPmethod WURun(WURunRequest, WURunResponse);
 
     ESPmethod WUExport(WUExportRequest, WUExportResponse);
 

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

@@ -902,6 +902,111 @@ bool CWsWorkunitsEx::onWUSubmit(IEspContext &context, IEspWUSubmitRequest &req,
     return true;
 }
 
+bool CWsWorkunitsEx::onWURun(IEspContext &context, IEspWURunRequest &req, IEspWURunResponse &resp)
+{
+    try
+    {
+        SCMStringBuffer wuid;
+        wuid.set(req.getWuid());
+
+        bool cloneWorkunit=req.getCloneWorkunit();
+        if (!wuid.length() && notEmpty(req.getQuerySet()) && notEmpty(req.getQuery()))
+        {
+            cloneWorkunit=true;
+            Owned<IPropertyTree> qstree = getQueryRegistry(req.getQuerySet(), true);
+            if (qstree)
+            {
+                IPropertyTree *query = NULL;
+                VStringBuffer xpath("Alias[@name=\"%s\"]", req.getQuery());
+                IPropertyTree *alias = qstree->queryPropTree(xpath.str());
+                if (alias)
+                {
+                    const char *quid = alias->queryProp("@id");
+                    if (!quid)
+                        throw MakeStringException(-1, "Alias %s/%s has no Query defined", req.getQuerySet(), req.getQuery());
+                    xpath.clear().appendf("Query[@id='%s']", quid);
+                    query = qstree->queryPropTree(xpath.str());
+                    if (!query)
+                        throw MakeStringException(-1, "Alias %s/%s refers to a non existing query %s", req.getQuerySet(), req.getQuery(), quid);
+                }
+                else
+                {
+                    xpath.clear().appendf("Query[@id=\"%s\"]", req.getQuery());
+                    query = qstree->queryPropTree(xpath.str());
+                }
+                if (query)
+                {
+                    if (query->getPropBool("@suspended"))
+                        throw MakeStringException(-1, "Query %s/%s is currently suspended", req.getQuerySet(), req.getQuery());
+
+                    wuid.set(query->queryProp("@wuid"));
+                }
+                else
+                    throw MakeStringException(-1, "Query %s/%s not found", req.getQuerySet(), req.getQuery());
+            }
+            else
+                throw MakeStringException(-1, "QuerySet %s not found", req.getQuerySet());
+        }
+
+        if (!wuid.length())
+            throw MakeStringException(ECLWATCH_MISSING_PARAMS,"Workunit or Query required");
+
+        ensureWsWorkunitAccess(context, wuid.str(), SecAccess_Write);
+
+        Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
+        if(cloneWorkunit)
+        {
+            Owned<IConstWorkUnit> src(factory->openWorkUnit(wuid.str(), false));
+            NewWsWorkunit wu(factory, context);
+            wu->getWuid(wuid);
+            queryExtendedWU(wu)->copyWorkUnit(src);
+
+            SCMStringBuffer token;
+            wu->setSecurityToken(createToken(wuid.str(), context.queryUserId(), context.queryPassword(), token).str());
+        }
+
+        Owned<IConstWorkUnit> cw(factory->openWorkUnit(wuid.str(), false));
+        if (!cw)
+            throw MakeStringException(ECLWATCH_CANNOT_UPDATE_WORKUNIT,"Cannot open workunit %s.", wuid.str());
+
+        if (notEmpty(req.getInput()))
+        {
+            Owned<IWuWebView> web = createWuWebView(*cw, NULL, getCFD(), true);
+            web->addInputsFromXml(req.getInput());
+        }
+
+        submitWsWorkunit(context, cw, req.getCluster(), NULL, 0, false, true);
+        cw.clear();
+
+        int timeToWait = req.getWait();
+        if (timeToWait != 0)
+            waitForWorkUnitToComplete(wuid.str(), timeToWait);
+
+
+        cw.set(factory->openWorkUnit(wuid.str(), false));
+        if (!cw)
+            throw MakeStringException(ECLWATCH_CANNOT_UPDATE_WORKUNIT,"Cannot open workunit %s.", wuid.str());
+
+        SCMStringBuffer stateDesc;
+        resp.setState(cw->getStateDesc(stateDesc).str());
+        resp.setWuid(wuid.str());
+
+        if (cw->getState()==WUStateCompleted)
+        {
+            SCMStringBuffer result;
+            getFullWorkUnitResultsXML(context.queryUserId(), context.queryPassword(), cw.get(), result, false, ExceptionSeverityInformation);
+            resp.setResults(result.str());
+        }
+
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+    return true;
+}
+
+
 bool CWsWorkunitsEx::onWUWaitCompiled(IEspContext &context, IEspWUWaitRequest &req, IEspWUWaitResponse &resp)
 {
     try

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

@@ -68,6 +68,7 @@ public:
     bool onWUAbort(IEspContext &context, IEspWUAbortRequest &req, IEspWUAbortResponse &resp);
     bool onWUSchedule(IEspContext &context, IEspWUScheduleRequest &req, IEspWUScheduleResponse &resp);
     bool onWUSubmit(IEspContext &context, IEspWUSubmitRequest &req, IEspWUSubmitResponse &resp);
+    bool onWURun(IEspContext &context, IEspWURunRequest &req, IEspWURunResponse &resp);
     bool onWUCreate(IEspContext &context, IEspWUCreateRequest &req, IEspWUCreateResponse &resp);
     bool onWUCreateAndUpdate(IEspContext &context, IEspWUUpdateRequest &req, IEspWUUpdateResponse &resp);
     bool onWUResubmit(IEspContext &context, IEspWUResubmitRequest &req, IEspWUResubmitResponse &resp);