Переглянути джерело

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 роки тому
батько
коміт
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);