LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1201 1228 97.8 %
Date: 2026-07-09 08:35:43 Functions: 24 24 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster/vector pipeline" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024-2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_conv.h"
      14             : #include "cpl_enumerate.h"
      15             : #include "cpl_error_internal.h"
      16             : #include "cpl_json.h"
      17             : 
      18             : #include "gdalalg_abstract_pipeline.h"
      19             : #include "gdalalg_materialize.h"
      20             : #include "gdalalg_raster_read.h"
      21             : #include "gdalalg_raster_write.h"
      22             : #include "gdalalg_vector_read.h"
      23             : #include "gdalalg_tee.h"
      24             : 
      25             : #include "vrtdataset.h"
      26             : 
      27             : #include <algorithm>
      28             : #include <cassert>
      29             : 
      30             : //! @cond Doxygen_Suppress
      31             : 
      32             : /* clang-format off */
      33             : constexpr const char *const apszReadParametersPrefixOmitted[] = {
      34             :     GDAL_ARG_NAME_INPUT,
      35             :     GDAL_ARG_NAME_INPUT_FORMAT,
      36             :     GDAL_ARG_NAME_OPEN_OPTION,
      37             :     GDAL_ARG_NAME_INPUT_LAYER};
      38             : 
      39             : constexpr const char *const apszWriteParametersPrefixOmitted[] = {
      40             :     GDAL_ARG_NAME_OUTPUT,
      41             :     GDAL_ARG_NAME_OUTPUT_FORMAT,
      42             :     GDAL_ARG_NAME_CREATION_OPTION,
      43             :     GDAL_ARG_NAME_OUTPUT_LAYER,
      44             :     GDAL_ARG_NAME_LAYER_CREATION_OPTION,
      45             :     GDAL_ARG_NAME_UPDATE,
      46             :     GDAL_ARG_NAME_OVERWRITE,
      47             :     GDAL_ARG_NAME_APPEND,
      48             :     GDAL_ARG_NAME_OVERWRITE_LAYER};
      49             : 
      50             : /* clang-format on */
      51             : 
      52             : /************************************************************************/
      53             : /*                       IsReadSpecificArgument()                       */
      54             : /************************************************************************/
      55             : 
      56             : /* static */
      57          36 : bool GDALAbstractPipelineAlgorithm::IsReadSpecificArgument(
      58             :     const char *pszArgName)
      59             : {
      60          36 :     return std::find_if(std::begin(apszReadParametersPrefixOmitted),
      61             :                         std::end(apszReadParametersPrefixOmitted),
      62         118 :                         [pszArgName](const char *pszStr)
      63         118 :                         { return strcmp(pszStr, pszArgName) == 0; }) !=
      64          36 :            std::end(apszReadParametersPrefixOmitted);
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*                      IsWriteSpecificArgument()                       */
      69             : /************************************************************************/
      70             : 
      71             : /* static */
      72          53 : bool GDALAbstractPipelineAlgorithm::IsWriteSpecificArgument(
      73             :     const char *pszArgName)
      74             : {
      75          53 :     return std::find_if(std::begin(apszWriteParametersPrefixOmitted),
      76             :                         std::end(apszWriteParametersPrefixOmitted),
      77         241 :                         [pszArgName](const char *pszStr)
      78         241 :                         { return strcmp(pszStr, pszArgName) == 0; }) !=
      79          53 :            std::end(apszWriteParametersPrefixOmitted);
      80             : }
      81             : 
      82             : /************************************************************************/
      83             : /*        GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep()        */
      84             : /************************************************************************/
      85             : 
      86         447 : bool GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep(
      87             :     const std::vector<GDALPipelineStepAlgorithm *> &steps,
      88             :     bool forAutoComplete) const
      89             : {
      90         447 :     if (m_bExpectReadStep && !steps.front()->CanBeFirstStep())
      91             :     {
      92           6 :         std::set<CPLString> setFirstStepNames;
      93         168 :         for (const auto &stepName : GetStepRegistry().GetNames())
      94             :         {
      95         330 :             auto alg = GetStepAlg(stepName);
      96         184 :             if (alg && alg->CanBeFirstStep() &&
      97          19 :                 stepName != GDALRasterReadAlgorithm::NAME)
      98             :             {
      99          34 :                 setFirstStepNames.insert(CPLString(stepName)
     100          34 :                                              .replaceAll(RASTER_SUFFIX, "")
     101          17 :                                              .replaceAll(VECTOR_SUFFIX, ""));
     102             :             }
     103             :         }
     104          15 :         std::vector<std::string> firstStepNames{GDALRasterReadAlgorithm::NAME};
     105          18 :         for (const std::string &s : setFirstStepNames)
     106          15 :             firstStepNames.push_back(s);
     107             : 
     108           3 :         std::string msg = "First step should be ";
     109          21 :         for (size_t i = 0; i < firstStepNames.size(); ++i)
     110             :         {
     111          18 :             if (i == firstStepNames.size() - 1)
     112           3 :                 msg += " or ";
     113          15 :             else if (i > 0)
     114          12 :                 msg += ", ";
     115          18 :             msg += '\'';
     116          18 :             msg += firstStepNames[i];
     117          18 :             msg += '\'';
     118             :         }
     119             : 
     120           3 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
     121           3 :         return false;
     122             :     }
     123             : 
     124         444 :     if (!m_bExpectReadStep)
     125             :     {
     126          20 :         if (steps.front()->CanBeFirstStep())
     127             :         {
     128           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     129             :                         "No read-like step like '%s' is allowed",
     130           1 :                         steps.front()->GetName().c_str());
     131           1 :             return false;
     132             :         }
     133             :     }
     134             : 
     135         443 :     if (forAutoComplete)
     136          26 :         return true;
     137             : 
     138         417 :     if (m_eLastStepAsWrite == StepConstraint::CAN_NOT_BE)
     139             :     {
     140          23 :         if (steps.back()->CanBeLastStep() && !steps.back()->CanBeMiddleStep())
     141             :         {
     142           2 :             ReportError(CE_Failure, CPLE_AppDefined,
     143             :                         "Last step in %s pipeline must not be a "
     144             :                         "write-like step.",
     145           2 :                         m_bInnerPipeline ? "an inner" : "a");
     146           2 :             return false;
     147             :         }
     148             :     }
     149             : 
     150         633 :     for (size_t i = 1; i < steps.size() - 1; ++i)
     151             :     {
     152         219 :         if (!steps[i]->CanBeMiddleStep())
     153             :         {
     154           7 :             if (steps[i]->CanBeFirstStep() && m_bExpectReadStep)
     155             :             {
     156           3 :                 ReportError(CE_Failure, CPLE_AppDefined,
     157             :                             "Only first step can be '%s'",
     158           3 :                             steps[i]->GetName().c_str());
     159             :             }
     160           8 :             else if (steps[i]->CanBeLastStep() &&
     161           4 :                      m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
     162             :             {
     163           3 :                 ReportError(CE_Failure, CPLE_AppDefined,
     164             :                             "Only last step can be '%s'",
     165           3 :                             steps[i]->GetName().c_str());
     166             :             }
     167             :             else
     168             :             {
     169           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     170             :                             "'%s' is not allowed as an intermediate step",
     171           1 :                             steps[i]->GetName().c_str());
     172           1 :                 return false;
     173             :             }
     174             :         }
     175             :     }
     176             : 
     177         417 :     if (steps.size() >= 2 && steps.back()->CanBeFirstStep() &&
     178           3 :         !steps.back()->CanBeLastStep())
     179             :     {
     180           2 :         ReportError(CE_Failure, CPLE_AppDefined,
     181             :                     "'%s' is only allowed as a first step",
     182           2 :                     steps.back()->GetName().c_str());
     183           2 :         return false;
     184             :     }
     185             : 
     186         440 :     if (m_eLastStepAsWrite == StepConstraint::MUST_BE &&
     187          28 :         !steps.back()->CanBeLastStep())
     188             :     {
     189           2 :         std::set<CPLString> setLastStepNames;
     190          87 :         for (const auto &stepName : GetStepRegistry().GetNames())
     191             :         {
     192         172 :             auto alg = GetStepAlg(stepName);
     193          86 :             if (alg && alg->CanBeLastStep())
     194             :             {
     195             :                 const CPLString nameWithoutSuffix =
     196          13 :                     CPLString(stepName)
     197          26 :                         .replaceAll(RASTER_SUFFIX, "")
     198          39 :                         .replaceAll(VECTOR_SUFFIX, "");
     199          13 :                 if (nameWithoutSuffix != GDALRasterWriteAlgorithm::NAME)
     200             :                 {
     201          11 :                     setLastStepNames.insert(nameWithoutSuffix);
     202             :                 }
     203             :             }
     204             :         }
     205           5 :         std::vector<std::string> lastStepNames{GDALRasterWriteAlgorithm::NAME};
     206          10 :         for (const std::string &s : setLastStepNames)
     207           9 :             lastStepNames.push_back(s);
     208             : 
     209           1 :         std::string msg = "Last step should be ";
     210          11 :         for (size_t i = 0; i < lastStepNames.size(); ++i)
     211             :         {
     212          10 :             if (i == lastStepNames.size() - 1)
     213           1 :                 msg += " or ";
     214           9 :             else if (i > 0)
     215           8 :                 msg += ", ";
     216          10 :             msg += '\'';
     217          10 :             msg += lastStepNames[i];
     218          10 :             msg += '\'';
     219             :         }
     220             : 
     221           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
     222           1 :         return false;
     223             :     }
     224             : 
     225         411 :     return true;
     226             : }
     227             : 
     228             : /************************************************************************/
     229             : /*             GDALAbstractPipelineAlgorithm::GetStepAlg()              */
     230             : /************************************************************************/
     231             : 
     232             : std::unique_ptr<GDALPipelineStepAlgorithm>
     233        3380 : GDALAbstractPipelineAlgorithm::GetStepAlg(const std::string &name) const
     234             : {
     235        6760 :     auto alg = GetStepRegistry().Instantiate(name);
     236             :     return std::unique_ptr<GDALPipelineStepAlgorithm>(
     237        6760 :         cpl::down_cast<GDALPipelineStepAlgorithm *>(alg.release()));
     238             : }
     239             : 
     240             : /************************************************************************/
     241             : /*                           GetDatasetType()                           */
     242             : /************************************************************************/
     243             : 
     244             : /** Return GDAL_OF_RASTER, GDAL_OF_VECTOR or 0 */
     245         747 : static int GetDatasetType(GDALDataset *poDS)
     246             : {
     247         747 :     if (poDS->GetLayerCount() > 0 && poDS->GetRasterCount() == 0)
     248         350 :         return GDAL_OF_VECTOR;
     249             : 
     250         794 :     if (poDS->GetLayerCount() == 0 &&
     251         397 :         (poDS->GetRasterCount() > 0 ||
     252          15 :          poDS->GetMetadata(GDAL_MDD_SUBDATASETS) != nullptr))
     253             :     {
     254         382 :         return GDAL_OF_RASTER;
     255             :     }
     256             : 
     257          15 :     return 0;
     258             : }
     259             : 
     260             : /************************************************************************/
     261             : /*                        GetInputDatasetType()                         */
     262             : /************************************************************************/
     263             : 
     264             : /** Return GDAL_OF_RASTER, GDAL_OF_VECTOR or 0 */
     265             : /* static */
     266         138 : int GDALAbstractPipelineAlgorithm::GetInputDatasetType(
     267             :     const GDALPipelineStepAlgorithm *alg)
     268             : {
     269         138 :     int ret = 0;
     270         138 :     const auto stepInputArg = alg->GetArg(GDAL_ARG_NAME_INPUT);
     271         158 :     if (stepInputArg && stepInputArg->IsExplicitlySet() &&
     272          20 :         (stepInputArg->GetType() == GAAT_DATASET ||
     273          10 :          stepInputArg->GetType() == GAAT_DATASET_LIST))
     274             :     {
     275          20 :         std::string inputDatasetName;
     276          10 :         if (stepInputArg->GetType() == GAAT_DATASET)
     277             :         {
     278             :             inputDatasetName =
     279           0 :                 stepInputArg->Get<GDALArgDatasetValue>().GetName();
     280             :         }
     281             :         else
     282             :         {
     283          10 :             auto &val = stepInputArg->Get<std::vector<GDALArgDatasetValue>>();
     284          10 :             if (!val.empty())
     285             :             {
     286          10 :                 inputDatasetName = val[0].GetName();
     287             :             }
     288             :         }
     289             : 
     290          10 :         if (!inputDatasetName.empty())
     291             :         {
     292          10 :             std::unique_ptr<GDALDataset> datasetHolder;
     293             :             GDALDataset *poDS;
     294             :             const auto oIter =
     295          10 :                 alg->m_oMapDatasetNameToDataset.find(inputDatasetName);
     296          10 :             if (oIter != alg->m_oMapDatasetNameToDataset.end())
     297           4 :                 poDS = oIter->second;
     298             :             else
     299             :             {
     300          12 :                 CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     301           6 :                 datasetHolder.reset(
     302             :                     GDALDataset::Open(inputDatasetName.c_str()));
     303           6 :                 poDS = datasetHolder.get();
     304             :             }
     305          10 :             if (poDS)
     306             :             {
     307          10 :                 ret = GetDatasetType(poDS);
     308             :             }
     309             :         }
     310             :     }
     311         138 :     return ret;
     312             : }
     313             : 
     314             : /************************************************************************/
     315             : /*    GDALAbstractPipelineAlgorithm::CopyStepAlgorithmFromAnother()     */
     316             : /************************************************************************/
     317             : 
     318             : /** Copy arguments and other parameters from \a src to \a dst, typically
     319             :  * when turning a raster algorithm to the vector one of the same name, or
     320             :  * vice-versa.
     321             :  */
     322          33 : bool GDALAbstractPipelineAlgorithm::CopyStepAlgorithmFromAnother(
     323             :     GDALPipelineStepAlgorithm *dst, const GDALPipelineStepAlgorithm *src,
     324             :     bool maybeWriteStep) const
     325             : {
     326          33 :     if (src->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
     327             :     {
     328             :         const auto poSrcTeeAlg =
     329           7 :             dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(src);
     330           7 :         auto poDstTeeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(dst);
     331           7 :         CPLAssert(poSrcTeeAlg);
     332           7 :         CPLAssert(poDstTeeAlg);
     333           7 :         poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
     334             :     }
     335             : 
     336          33 :     if (maybeWriteStep)
     337             :     {
     338             :         // Propagate output parameters set at the pipeline level to the
     339             :         // "write" step
     340         393 :         for (auto &arg : dst->GetArgs())
     341             :         {
     342         370 :             if (!arg->IsHidden())
     343             :             {
     344         322 :                 const auto pipelineArg = GetArg(arg->GetName());
     345         326 :                 if (pipelineArg && pipelineArg->IsExplicitlySet() &&
     346           4 :                     pipelineArg->GetType() == arg->GetType())
     347             :                 {
     348           4 :                     arg->SetSkipIfAlreadySet(true);
     349           4 :                     [[maybe_unused]] bool ret = arg->SetFrom(*pipelineArg);
     350           4 :                     CPLAssert(ret);
     351             :                 }
     352             :             }
     353             :         }
     354             :     }
     355             : 
     356             :     // Propagate parameters set on the old algorithm to the new one
     357         410 :     for (const auto &srcArg : src->GetArgs())
     358             :     {
     359         378 :         if (srcArg->IsExplicitlySet())
     360             :         {
     361          40 :             auto dstArg = dst->GetArg(srcArg->GetName());
     362          40 :             if (!dstArg)
     363             :             {
     364           1 :                 dst->ReportError(CE_Failure, CPLE_IllegalArg,
     365             :                                  "Option '--%s' is unknown",
     366           1 :                                  srcArg->GetName().c_str());
     367           1 :                 return false;
     368             :             }
     369             :             else
     370             :             {
     371          39 :                 dstArg->SetSkipIfAlreadySet(true);
     372          39 :                 if (!dstArg->SetFrom(*srcArg))
     373           0 :                     return false;
     374             :             }
     375             :         }
     376             :     }
     377             : 
     378             :     dst->m_oMapDatasetNameToDataset =
     379          32 :         std::move(src->m_oMapDatasetNameToDataset);
     380          64 :     dst->SetCallPath({dst->GetName()});
     381          32 :     dst->SetReferencePathForRelativePaths(GetReferencePathForRelativePaths());
     382          32 :     if (IsCalledFromCommandLine())
     383           2 :         dst->SetCalledFromCommandLine();
     384             : 
     385          32 :     return true;
     386             : }
     387             : 
     388             : /************************************************************************/
     389             : /*      GDALAbstractPipelineAlgorithm::ParseCommandLineArguments()      */
     390             : /************************************************************************/
     391             : 
     392         464 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
     393             :     const std::vector<std::string> &argsIn)
     394             : {
     395         464 :     return ParseCommandLineArguments(argsIn, /*forAutoComplete=*/false,
     396         464 :                                      /*pCurArgsForAutocomplete=*/nullptr);
     397             : }
     398             : 
     399             : /** Parse arguments of a pipeline.
     400             :  *
     401             :  * @param argsIn Pipeline arguments
     402             :  * @param forAutoComplete true if this method is called from GetAutoComplete()
     403             :  * @param[out] pCurArgsForAutocomplete Pointer to a vector of string, or null.
     404             :  *                                     If provided, it will contain the arguments
     405             :  *                                     of the active pipeline. Useful for
     406             :  *                                     completion in nested pipelines.
     407             :  */
     408         544 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
     409             :     const std::vector<std::string> &argsIn, bool forAutoComplete,
     410             :     std::vector<std::string> *pCurArgsForAutocomplete)
     411             : {
     412        1088 :     std::vector<std::string> args = argsIn;
     413         544 :     if (pCurArgsForAutocomplete)
     414          55 :         *pCurArgsForAutocomplete = args;
     415             : 
     416         544 :     if (!m_bInnerPipeline && IsCalledFromCommandLine())
     417             :     {
     418         122 :         m_eLastStepAsWrite = StepConstraint::MUST_BE;
     419             :     }
     420             : 
     421         610 :     if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
     422          66 :                              args[0] == "help" || args[0] == "--json-usage"))
     423             :     {
     424           5 :         return GDALAlgorithm::ParseCommandLineArguments(args);
     425             :     }
     426         539 :     else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
     427             :     {
     428          12 :         m_helpDocCategory = args[0].substr(strlen("--help-doc="));
     429          24 :         return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
     430             :     }
     431             : 
     432         527 :     bool foundStepMarker = false;
     433             : 
     434        3912 :     for (size_t i = 0; i < args.size(); ++i)
     435             :     {
     436        3395 :         const auto &arg = args[i];
     437        3395 :         if (arg == "--pipeline")
     438             :         {
     439          10 :             if (i + 1 < args.size() &&
     440          10 :                 CPLString(args[i + 1]).ifind(".json") != std::string::npos)
     441           2 :                 break;
     442           3 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     443             :         }
     444             : 
     445        3390 :         else if (cpl::starts_with(arg, "--pipeline="))
     446             :         {
     447           2 :             if (CPLString(arg).ifind(".json") != std::string::npos)
     448           1 :                 break;
     449           1 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     450             :         }
     451             : 
     452             :         // gdal pipeline [--quiet] "read poly.gpkg ..."
     453        3388 :         if (arg.find("read ") == 0)
     454           3 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     455             : 
     456        3385 :         if (arg == "!")
     457         627 :             foundStepMarker = true;
     458             :     }
     459             : 
     460         520 :     bool runExistingPipeline = false;
     461         520 :     if (!foundStepMarker && !m_executionForStreamOutput)
     462             :     {
     463         103 :         std::string osCommandLine;
     464         288 :         for (const auto &arg : args)
     465             :         {
     466         212 :             if (((!arg.empty() && arg[0] != '-') ||
     467         425 :                  cpl::starts_with(arg, "--pipeline=")) &&
     468         405 :                 CPLString(arg).ifind(".json") != std::string::npos)
     469             :             {
     470             :                 bool ret;
     471          27 :                 if (m_pipeline == arg)
     472           2 :                     ret = true;
     473             :                 else
     474             :                 {
     475             :                     const std::string filename =
     476          25 :                         cpl::starts_with(arg, "--pipeline=")
     477             :                             ? arg.substr(strlen("--pipeline="))
     478          50 :                             : arg;
     479          25 :                     if (forAutoComplete)
     480             :                     {
     481           6 :                         SetParseForAutoCompletion();
     482             :                     }
     483          25 :                     ret = GDALAlgorithm::ParseCommandLineArguments(args) ||
     484             :                           forAutoComplete;
     485          25 :                     if (ret)
     486             :                     {
     487          22 :                         ret = m_pipeline == filename;
     488             :                     }
     489             :                 }
     490          27 :                 if (ret)
     491             :                 {
     492          24 :                     CPLJSONDocument oDoc;
     493          24 :                     ret = oDoc.Load(m_pipeline);
     494          24 :                     if (ret)
     495             :                     {
     496             :                         osCommandLine =
     497          23 :                             oDoc.GetRoot().GetString("command_line");
     498          23 :                         if (osCommandLine.empty())
     499             :                         {
     500           1 :                             ReportError(CE_Failure, CPLE_AppDefined,
     501             :                                         "command_line missing in %s",
     502             :                                         m_pipeline.c_str());
     503           1 :                             return false;
     504             :                         }
     505             : 
     506          66 :                         for (const char *prefix :
     507             :                              {"gdal pipeline ", "gdal raster pipeline ",
     508          88 :                               "gdal vector pipeline "})
     509             :                         {
     510          66 :                             if (cpl::starts_with(osCommandLine, prefix))
     511             :                                 osCommandLine =
     512          22 :                                     osCommandLine.substr(strlen(prefix));
     513             :                         }
     514             : 
     515          22 :                         if (oDoc.GetRoot().GetBool(
     516             :                                 "relative_paths_relative_to_this_file", true))
     517             :                         {
     518           0 :                             SetReferencePathForRelativePaths(
     519           0 :                                 CPLGetPathSafe(m_pipeline.c_str()).c_str());
     520             :                         }
     521             : 
     522          22 :                         runExistingPipeline = true;
     523             :                     }
     524             :                 }
     525          26 :                 if (ret)
     526          22 :                     break;
     527             :                 else
     528           4 :                     return false;
     529             :             }
     530             :         }
     531          98 :         if (runExistingPipeline)
     532             :         {
     533             :             const CPLStringList aosArgs(
     534          22 :                 CSLTokenizeString(osCommandLine.c_str()));
     535             : 
     536          22 :             args = aosArgs;
     537             :         }
     538             :     }
     539             : 
     540         515 :     if (!m_steps.empty())
     541             :     {
     542           3 :         ReportError(CE_Failure, CPLE_AppDefined,
     543             :                     "ParseCommandLineArguments() can only be called once per "
     544             :                     "instance.");
     545           3 :         return false;
     546             :     }
     547             : 
     548             :     const bool bIsGenericPipeline =
     549         512 :         (GetInputType() == (GDAL_OF_RASTER | GDAL_OF_VECTOR));
     550             : 
     551             :     struct Step
     552             :     {
     553             :         std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
     554             :         std::vector<std::string> args{};
     555             :         bool alreadyChangedType = false;
     556             :         bool isSubAlgorithm = false;
     557             :     };
     558             : 
     559         512 :     int nDatasetType = GetInputType();
     560             :     const auto SetCurStepAlg =
     561        1118 :         [this, bIsGenericPipeline, &nDatasetType](
     562        4605 :             Step &curStep, const std::string &algName, bool firstStep)
     563             :     {
     564        1118 :         if (bIsGenericPipeline)
     565             :         {
     566         488 :             if (algName == GDALRasterReadAlgorithm::NAME)
     567             :             {
     568         199 :                 curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
     569             :             }
     570             :             else
     571             :             {
     572         289 :                 if (nDatasetType == GDAL_OF_RASTER)
     573          53 :                     curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
     574         236 :                 else if (nDatasetType == GDAL_OF_VECTOR)
     575          24 :                     curStep.alg = GetStepAlg(algName + VECTOR_SUFFIX);
     576         289 :                 if (!curStep.alg)
     577         223 :                     curStep.alg = GetStepAlg(algName);
     578         289 :                 if (!curStep.alg)
     579         133 :                     curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
     580         289 :                 if (curStep.alg)
     581         284 :                     nDatasetType = curStep.alg->GetOutputType();
     582             :             }
     583             :         }
     584             :         else
     585             :         {
     586         630 :             curStep.alg = GetStepAlg(algName);
     587             :         }
     588        1118 :         if (!curStep.alg)
     589             :         {
     590          14 :             ReportError(CE_Failure, CPLE_AppDefined, "unknown step name: %s",
     591             :                         algName.c_str());
     592          14 :             return false;
     593             :         }
     594             :         // We don't want to accept '_PIPE_' dataset placeholder for the first
     595             :         // step of a pipeline.
     596        1104 :         curStep.alg->m_inputDatasetCanBeOmitted =
     597        1104 :             !firstStep || !m_bExpectReadStep;
     598        2208 :         curStep.alg->SetCallPath({algName});
     599        1104 :         curStep.alg->SetReferencePathForRelativePaths(
     600             :             GetReferencePathForRelativePaths());
     601        1104 :         return true;
     602         512 :     };
     603             : 
     604        1024 :     std::vector<Step> steps;
     605         512 :     steps.resize(1);
     606             : 
     607         512 :     int nNestLevel = 0;
     608        1024 :     std::vector<std::string> nestedPipelineArgs;
     609             : 
     610        3890 :     for (const auto &argIn : args)
     611             :     {
     612        3412 :         std::string arg(argIn);
     613             : 
     614             :         // If outputting to stdout, automatically turn off progress bar
     615        3412 :         if (arg == "/vsistdout/")
     616             :         {
     617           2 :             auto quietArg = GetArg(GDAL_ARG_NAME_QUIET);
     618           2 :             if (quietArg && quietArg->GetType() == GAAT_BOOLEAN)
     619           2 :                 quietArg->Set(true);
     620             :         }
     621             : 
     622        3412 :         auto &curStep = steps.back();
     623             : 
     624        3412 :         if (nNestLevel > 0)
     625             :         {
     626         211 :             if (arg == CLOSE_NESTED_PIPELINE)
     627             :             {
     628          53 :                 if ((--nNestLevel) == 0)
     629             :                 {
     630         104 :                     arg = BuildNestedPipeline(curStep.alg.get(),
     631             :                                               nestedPipelineArgs,
     632          52 :                                               forAutoComplete, nullptr);
     633          52 :                     if (arg.empty())
     634             :                     {
     635           8 :                         return false;
     636             :                     }
     637          44 :                     if (pCurArgsForAutocomplete)
     638           1 :                         *pCurArgsForAutocomplete = args;
     639             :                 }
     640             :                 else
     641             :                 {
     642           1 :                     nestedPipelineArgs.push_back(std::move(arg));
     643           1 :                     continue;
     644             :                 }
     645             :             }
     646             :             else
     647             :             {
     648         158 :                 if (arg == OPEN_NESTED_PIPELINE)
     649             :                 {
     650           3 :                     if (++nNestLevel == MAX_NESTING_LEVEL)
     651             :                     {
     652           1 :                         ReportError(CE_Failure, CPLE_AppDefined,
     653             :                                     "Too many nested pipelines");
     654           1 :                         return false;
     655             :                     }
     656             :                 }
     657         157 :                 nestedPipelineArgs.push_back(std::move(arg));
     658         157 :                 continue;
     659             :             }
     660             :         }
     661             : 
     662        3245 :         if (arg == "--progress")
     663             :         {
     664           5 :             m_progressBarRequested = true;
     665           5 :             continue;
     666             :         }
     667        3240 :         if (arg == "-q" || arg == "--quiet")
     668             :         {
     669           0 :             m_quiet = true;
     670           0 :             m_progressBarRequested = false;
     671           0 :             continue;
     672             :         }
     673             : 
     674        3240 :         if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
     675             :         {
     676           8 :             if (!steps.back().alg)
     677           2 :                 steps.pop_back();
     678           8 :             if (steps.empty())
     679             :             {
     680           2 :                 return GDALAlgorithm::ParseCommandLineArguments(args);
     681             :             }
     682             :             else
     683             :             {
     684           6 :                 m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
     685           6 :                 return true;
     686             :             }
     687             :         }
     688             : 
     689        3232 :         if (arg == "!" || arg == "|")
     690             :         {
     691         638 :             if (curStep.alg)
     692             :             {
     693         622 :                 steps.resize(steps.size() + 1);
     694             :             }
     695             :         }
     696        2594 :         else if (arg == OPEN_NESTED_PIPELINE)
     697             :         {
     698          56 :             if (!curStep.alg)
     699             :             {
     700           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     701             :                             "Open bracket must be placed where an input "
     702             :                             "dataset is expected");
     703           1 :                 return false;
     704             :             }
     705          55 :             ++nNestLevel;
     706             :         }
     707        2538 :         else if (arg == CLOSE_NESTED_PIPELINE)
     708             :         {
     709           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     710             :                         "Closing bracket found without matching open bracket");
     711           1 :             return false;
     712             :         }
     713             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     714        2537 :         else if (arg == "+step")
     715             :         {
     716           8 :             if (curStep.alg)
     717             :             {
     718           4 :                 steps.resize(steps.size() + 1);
     719             :             }
     720             :         }
     721        2529 :         else if (arg.find("+gdal=") == 0)
     722             :         {
     723           6 :             const std::string algName = arg.substr(strlen("+gdal="));
     724           6 :             if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
     725           2 :                 return false;
     726             :         }
     727             : #endif
     728        2523 :         else if (!curStep.alg)
     729             :         {
     730        1112 :             std::string algName = std::move(arg);
     731             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     732        1112 :             if (!algName.empty() && algName[0] == '+')
     733           2 :                 algName = algName.substr(1);
     734             : #endif
     735        1112 :             if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
     736          12 :                 return false;
     737             :         }
     738             :         else
     739             :         {
     740        1411 :             if (curStep.alg->HasSubAlgorithms())
     741             :             {
     742             :                 auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
     743             :                     cpl::down_cast<GDALPipelineStepAlgorithm *>(
     744           3 :                         curStep.alg->InstantiateSubAlgorithm(arg).release()));
     745           3 :                 if (!subAlg)
     746             :                 {
     747           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     748             :                                 "'%s' is a unknown sub-algorithm of '%s'",
     749           1 :                                 arg.c_str(), curStep.alg->GetName().c_str());
     750           1 :                     return false;
     751             :                 }
     752           2 :                 curStep.isSubAlgorithm = true;
     753           2 :                 subAlg->m_inputDatasetCanBeOmitted =
     754           2 :                     steps.size() > 1 || !m_bExpectReadStep;
     755           2 :                 curStep.alg = std::move(subAlg);
     756           2 :                 continue;
     757             :             }
     758             : 
     759             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     760        1417 :             if (!arg.empty() && arg[0] == '+' &&
     761           9 :                 arg.find(' ') == std::string::npos)
     762             :             {
     763           6 :                 curStep.args.push_back("--" + arg.substr(1));
     764           6 :                 continue;
     765             :             }
     766             : #endif
     767        1402 :             curStep.args.push_back(std::move(arg));
     768             :         }
     769             :     }
     770             : 
     771         478 :     if (nNestLevel > 0)
     772             :     {
     773           2 :         if (forAutoComplete)
     774             :         {
     775           1 :             BuildNestedPipeline(steps.back().alg.get(), nestedPipelineArgs,
     776             :                                 forAutoComplete, pCurArgsForAutocomplete);
     777           1 :             return true;
     778             :         }
     779             :         else
     780             :         {
     781           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     782             :                         "Open bracket has no matching closing bracket");
     783           1 :             return false;
     784             :         }
     785             :     }
     786             : 
     787             :     // As we initially added a step without alg to bootstrap things, make
     788             :     // sure to remove it if it hasn't been filled, or the user has terminated
     789             :     // the pipeline with a '!' separator.
     790         476 :     if (!steps.back().alg)
     791          17 :         steps.pop_back();
     792             : 
     793         476 :     if (runExistingPipeline)
     794             :     {
     795             :         // Add a final "write" step if there is no explicit allowed last step
     796          22 :         if (!steps.empty() && !steps.back().alg->CanBeLastStep())
     797             :         {
     798          18 :             steps.resize(steps.size() + 1);
     799          36 :             steps.back().alg = GetStepAlg(
     800          36 :                 std::string(GDALRasterWriteAlgorithm::NAME)
     801          36 :                     .append(bIsGenericPipeline ? RASTER_SUFFIX : ""));
     802          18 :             steps.back().alg->m_inputDatasetCanBeOmitted = true;
     803             :         }
     804             : 
     805             :         // Remove "--output-format=stream" and "streamed_dataset" if found
     806          22 :         if (steps.back().alg->GetName() == GDALRasterWriteAlgorithm::NAME)
     807             :         {
     808          30 :             for (auto oIter = steps.back().args.begin();
     809          30 :                  oIter != steps.back().args.end();)
     810             :             {
     811          24 :                 if (*oIter == std::string("--")
     812          16 :                                   .append(GDAL_ARG_NAME_OUTPUT_FORMAT)
     813          12 :                                   .append("=stream") ||
     814          16 :                     *oIter == std::string("--")
     815           4 :                                   .append(GDAL_ARG_NAME_OUTPUT)
     816          32 :                                   .append("=streamed_dataset") ||
     817           4 :                     *oIter == "streamed_dataset")
     818             :                 {
     819           8 :                     oIter = steps.back().args.erase(oIter);
     820             :                 }
     821             :                 else
     822             :                 {
     823           0 :                     ++oIter;
     824             :                 }
     825             :             }
     826             :         }
     827             :     }
     828             : 
     829         476 :     bool helpRequested = false;
     830         476 :     if (IsCalledFromCommandLine())
     831             :     {
     832         245 :         for (auto &step : steps)
     833         163 :             step.alg->SetCalledFromCommandLine();
     834             : 
     835         481 :         for (const std::string &v : args)
     836             :         {
     837         399 :             if (cpl::ends_with(v, "=?"))
     838           3 :                 helpRequested = true;
     839             :         }
     840             :     }
     841             : 
     842         476 :     if (m_eLastStepAsWrite == StepConstraint::MUST_BE)
     843             :     {
     844          77 :         if (steps.size() < 2)
     845             :         {
     846          23 :             if (!steps.empty() && helpRequested)
     847             :             {
     848           1 :                 steps.back().alg->ParseCommandLineArguments(steps.back().args);
     849           1 :                 return false;
     850             :             }
     851             : 
     852          22 :             ReportError(CE_Failure, CPLE_AppDefined,
     853             :                         "At least 2 steps must be provided");
     854          22 :             return false;
     855             :         }
     856             : 
     857          54 :         if (!steps.back().alg->CanBeLastStep())
     858             :         {
     859          18 :             if (helpRequested)
     860             :             {
     861           2 :                 steps.back().alg->ParseCommandLineArguments(steps.back().args);
     862           2 :                 return false;
     863             :             }
     864             :         }
     865             :     }
     866             :     else
     867             :     {
     868         399 :         if (steps.empty())
     869             :         {
     870           4 :             ReportError(CE_Failure, CPLE_AppDefined,
     871             :                         "At least one step must be provided in %s pipeline.",
     872           4 :                         m_bInnerPipeline ? "an inner" : "a");
     873           4 :             return false;
     874             :         }
     875             :     }
     876             : 
     877         894 :     std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
     878        1507 :     for (const auto &step : steps)
     879        1060 :         stepAlgs.push_back(step.alg.get());
     880         447 :     if (!CheckFirstAndLastStep(stepAlgs, forAutoComplete))
     881          10 :         return false;  // CheckFirstAndLastStep emits an error
     882             : 
     883        1477 :     for (auto &step : steps)
     884             :     {
     885        1040 :         step.alg->SetReferencePathForRelativePaths(
     886             :             GetReferencePathForRelativePaths());
     887             :     }
     888             : 
     889             :     const auto PropagateArgsFromPipeline =
     890         736 :         [this](GDALPipelineStepAlgorithm *alg)
     891             :     {
     892         736 :         const GDALAbstractPipelineAlgorithm *constThis = this;
     893        8620 :         for (auto &arg : alg->GetArgs())
     894             :         {
     895        7884 :             if (!arg->IsHidden())
     896             :             {
     897        6728 :                 const auto pipelineArg = constThis->GetArg(arg->GetName());
     898        6841 :                 if (pipelineArg && pipelineArg->IsExplicitlySet() &&
     899         113 :                     pipelineArg->GetType() == arg->GetType())
     900             :                 {
     901         111 :                     arg->SetSkipIfAlreadySet(true);
     902         111 :                     arg->SetFrom(*pipelineArg);
     903             :                 }
     904             :             }
     905             :         }
     906         736 :     };
     907             : 
     908             :     // Propagate input parameters set at the pipeline level to the
     909             :     // "read" step
     910         437 :     if (m_bExpectReadStep)
     911             :     {
     912         418 :         PropagateArgsFromPipeline(steps.front().alg.get());
     913             :     }
     914             : 
     915             :     // Same with "write" step
     916         852 :     if (m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE &&
     917         415 :         steps.back().alg->CanBeLastStep())
     918             :     {
     919         318 :         PropagateArgsFromPipeline(steps.back().alg.get());
     920             :     }
     921             : 
     922         437 :     if (runExistingPipeline)
     923             :     {
     924          22 :         std::set<std::pair<Step *, std::string>> alreadyCleanedArgs;
     925             : 
     926         297 :         for (const auto &arg : GetArgs())
     927             :         {
     928         809 :             if (arg->IsUserProvided() ||
     929         506 :                 ((arg->GetName() == GDAL_ARG_NAME_INPUT ||
     930         484 :                   arg->GetName() == GDAL_ARG_NAME_INPUT_LAYER ||
     931         462 :                   arg->GetName() == GDAL_ARG_NAME_OUTPUT ||
     932         286 :                   arg->GetName() == GDAL_ARG_NAME_OUTPUT_FORMAT) &&
     933          66 :                  arg->IsExplicitlySet()))
     934             :             {
     935             :                 CPLStringList tokens(
     936          36 :                     CSLTokenizeString2(arg->GetName().c_str(), ".", 0));
     937          36 :                 std::string stepName;
     938          36 :                 std::string stepArgName;
     939          36 :                 if (tokens.size() == 1 && IsReadSpecificArgument(tokens[0]))
     940             :                 {
     941           3 :                     stepName = steps.front().alg->GetName();
     942           3 :                     stepArgName = tokens[0];
     943             :                 }
     944          55 :                 else if (tokens.size() == 1 &&
     945          22 :                          IsWriteSpecificArgument(tokens[0]))
     946             :                 {
     947          18 :                     stepName = steps.back().alg->GetName();
     948          18 :                     stepArgName = tokens[0];
     949             :                 }
     950          15 :                 else if (tokens.size() == 2)
     951             :                 {
     952          10 :                     stepName = tokens[0];
     953          10 :                     stepArgName = tokens[1];
     954             :                 }
     955             :                 else
     956             :                 {
     957           5 :                     if (tokens.size() == 1)
     958             :                     {
     959           4 :                         const Step *matchingStep = nullptr;
     960          15 :                         for (auto &step : steps)
     961             :                         {
     962          12 :                             if (step.alg->GetArg(tokens[0]))
     963             :                             {
     964           4 :                                 if (!matchingStep)
     965           3 :                                     matchingStep = &step;
     966             :                                 else
     967             :                                 {
     968           1 :                                     ReportError(
     969             :                                         CE_Failure, CPLE_AppDefined,
     970             :                                         "Ambiguous argument name '%s', because "
     971             :                                         "it is valid for several steps in the "
     972             :                                         "pipeline. It should be specified with "
     973             :                                         "the form "
     974             :                                         "<algorithm-name>.<argument-name>.",
     975             :                                         tokens[0]);
     976           1 :                                     return false;
     977             :                                 }
     978             :                             }
     979             :                         }
     980           3 :                         if (!matchingStep)
     981             :                         {
     982           1 :                             ReportError(CE_Failure, CPLE_AppDefined,
     983             :                                         "No step in the pipeline has an "
     984             :                                         "argument named '%s'",
     985             :                                         tokens[0]);
     986           1 :                             return false;
     987             :                         }
     988           2 :                         stepName = matchingStep->alg->GetName();
     989           2 :                         stepArgName = tokens[0];
     990             :                     }
     991             :                     else
     992             :                     {
     993           1 :                         ReportError(
     994             :                             CE_Failure, CPLE_AppDefined,
     995             :                             "Invalid argument name '%s'. It should of the "
     996             :                             "form <algorithm-name>.<argument-name>.",
     997           1 :                             arg->GetName().c_str());
     998           1 :                         return false;
     999             :                     }
    1000             :                 }
    1001          33 :                 const auto nPosBracket = stepName.find('[');
    1002          33 :                 int iRequestedStepIdx = -1;
    1003          33 :                 if (nPosBracket != std::string::npos && stepName.back() == ']')
    1004             :                 {
    1005             :                     iRequestedStepIdx =
    1006           3 :                         atoi(stepName.c_str() + nPosBracket + 1);
    1007           3 :                     stepName.resize(nPosBracket);
    1008             :                 }
    1009          33 :                 int iMatchingStepIdx = 0;
    1010          33 :                 Step *matchingStep = nullptr;
    1011         133 :                 for (auto &step : steps)
    1012             :                 {
    1013         103 :                     if (step.alg->GetName() == stepName)
    1014             :                     {
    1015          35 :                         if (iRequestedStepIdx >= 0)
    1016             :                         {
    1017           5 :                             if (iRequestedStepIdx == iMatchingStepIdx)
    1018             :                             {
    1019           2 :                                 matchingStep = &step;
    1020           2 :                                 break;
    1021             :                             }
    1022           3 :                             ++iMatchingStepIdx;
    1023             :                         }
    1024          30 :                         else if (matchingStep == nullptr)
    1025             :                         {
    1026          29 :                             matchingStep = &step;
    1027             :                         }
    1028             :                         else
    1029             :                         {
    1030           2 :                             ReportError(
    1031             :                                 CE_Failure, CPLE_AppDefined,
    1032             :                                 "Argument '%s' is ambiguous as there are "
    1033             :                                 "several '%s' steps in the pipeline. Qualify "
    1034             :                                 "it as '%s[<zero-based-index>]' to remove "
    1035             :                                 "ambiguity.",
    1036           1 :                                 arg->GetName().c_str(), stepName.c_str(),
    1037             :                                 stepName.c_str());
    1038           1 :                             return false;
    1039             :                         }
    1040             :                     }
    1041             :                 }
    1042          32 :                 if (!matchingStep)
    1043             :                 {
    1044           4 :                     ReportError(CE_Failure, CPLE_AppDefined,
    1045             :                                 "Argument '%s' refers to a non-existing '%s' "
    1046             :                                 "step in the pipeline.",
    1047           2 :                                 arg->GetName().c_str(), tokens[0]);
    1048           2 :                     return false;
    1049             :                 }
    1050             : 
    1051          30 :                 auto &step = *matchingStep;
    1052             :                 std::string stepArgNameDashDash =
    1053          90 :                     std::string("--").append(stepArgName);
    1054             : 
    1055          60 :                 auto oKeyPair = std::make_pair(matchingStep, stepArgName);
    1056          30 :                 if (!cpl::contains(alreadyCleanedArgs, oKeyPair))
    1057             :                 {
    1058          30 :                     alreadyCleanedArgs.insert(std::move(oKeyPair));
    1059             : 
    1060          60 :                     std::vector<GDALAlgorithmArg *> positionalArgs;
    1061         378 :                     for (auto &stepArg : step.alg->GetArgs())
    1062             :                     {
    1063         348 :                         if (stepArg->IsPositional())
    1064          23 :                             positionalArgs.push_back(stepArg.get());
    1065             :                     }
    1066             : 
    1067             :                     // Remove step arguments that match the user override
    1068             :                     const std::string stepArgNameDashDashEqual =
    1069          60 :                         stepArgNameDashDash + '=';
    1070          30 :                     size_t idxPositional = 0;
    1071          44 :                     for (auto oIter = step.args.begin();
    1072          44 :                          oIter != step.args.end();)
    1073             :                     {
    1074          14 :                         const auto &iterArgName = *oIter;
    1075          14 :                         if (iterArgName == stepArgNameDashDash)
    1076             :                         {
    1077           1 :                             oIter = step.args.erase(oIter);
    1078           1 :                             auto stepArg = step.alg->GetArg(stepArgName);
    1079           1 :                             if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
    1080             :                             {
    1081           1 :                                 if (oIter != step.args.end())
    1082           1 :                                     oIter = step.args.erase(oIter);
    1083             :                             }
    1084             :                         }
    1085          13 :                         else if (cpl::starts_with(iterArgName,
    1086             :                                                   stepArgNameDashDashEqual))
    1087             :                         {
    1088           3 :                             oIter = step.args.erase(oIter);
    1089             :                         }
    1090          10 :                         else if (!iterArgName.empty() && iterArgName[0] == '-')
    1091             :                         {
    1092           5 :                             const auto equalPos = iterArgName.find('=');
    1093          10 :                             auto stepArg = step.alg->GetArg(
    1094             :                                 equalPos == std::string::npos
    1095          10 :                                     ? iterArgName
    1096             :                                     : iterArgName.substr(0, equalPos));
    1097           5 :                             if (stepArg && stepArg->GetName() == stepArgName)
    1098             :                             {
    1099           1 :                                 oIter = step.args.erase(oIter);
    1100           1 :                                 if (equalPos == std::string::npos &&
    1101           2 :                                     stepArg->GetType() != GAAT_BOOLEAN &&
    1102           2 :                                     oIter != step.args.end())
    1103             :                                 {
    1104           1 :                                     oIter = step.args.erase(oIter);
    1105             :                                 }
    1106             :                             }
    1107             :                             else
    1108             :                             {
    1109           4 :                                 ++oIter;
    1110           4 :                                 if (stepArg && equalPos == std::string::npos &&
    1111           8 :                                     stepArg->GetType() != GAAT_BOOLEAN &&
    1112           5 :                                     oIter != step.args.end())
    1113             :                                 {
    1114           1 :                                     ++oIter;
    1115             :                                 }
    1116             :                             }
    1117             :                         }
    1118           5 :                         else if (idxPositional < positionalArgs.size())
    1119             :                         {
    1120           4 :                             if (positionalArgs[idxPositional]->GetName() ==
    1121             :                                 stepArgName)
    1122             :                             {
    1123           2 :                                 oIter = step.args.erase(oIter);
    1124             :                             }
    1125             :                             else
    1126             :                             {
    1127           2 :                                 ++oIter;
    1128             :                             }
    1129           4 :                             ++idxPositional;
    1130             :                         }
    1131             :                         else
    1132             :                         {
    1133           1 :                             ++oIter;
    1134             :                         }
    1135             :                     }
    1136             :                 }
    1137             : 
    1138          30 :                 if (arg->IsUserProvided())
    1139             :                 {
    1140             :                     // Add user override
    1141          11 :                     step.args.push_back(std::move(stepArgNameDashDash));
    1142          11 :                     auto stepArg = step.alg->GetArg(stepArgName);
    1143          11 :                     if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
    1144             :                     {
    1145           9 :                         step.args.push_back(arg->Get<std::string>());
    1146             :                     }
    1147             :                 }
    1148             :             }
    1149             :         }
    1150             :     }
    1151             : 
    1152         431 :     int nInitialDatasetType = 0;
    1153         431 :     if (bIsGenericPipeline)
    1154             :     {
    1155         185 :         if (!m_bExpectReadStep)
    1156             :         {
    1157          19 :             CPLAssert(m_inputDataset.size() == 1 &&
    1158             :                       m_inputDataset[0].GetDatasetRef());
    1159             :             nInitialDatasetType =
    1160          19 :                 GetDatasetType(m_inputDataset[0].GetDatasetRef());
    1161             :         }
    1162             : 
    1163             :         // Parse each step, but without running the validation
    1164         185 :         nDatasetType = nInitialDatasetType;
    1165         185 :         bool firstStep = nDatasetType == 0;
    1166             : 
    1167         604 :         for (auto &step : steps)
    1168             :         {
    1169         427 :             bool ret = false;
    1170         427 :             CPLErrorAccumulator oAccumulator;
    1171         427 :             bool hasTriedRaster = false;
    1172         427 :             if (nDatasetType == 0 || nDatasetType == GDAL_OF_RASTER)
    1173             :             {
    1174         403 :                 hasTriedRaster = true;
    1175             :                 [[maybe_unused]] auto context =
    1176         806 :                     oAccumulator.InstallForCurrentScope();
    1177         403 :                 step.alg->m_skipValidationInParseCommandLine = true;
    1178         403 :                 ret = step.alg->ParseCommandLineArguments(step.args);
    1179         403 :                 if (ret && nDatasetType == 0 && forAutoComplete)
    1180             :                 {
    1181          18 :                     ret = step.alg->ValidateArguments();
    1182          33 :                     if (ret && firstStep &&
    1183          15 :                         step.alg->m_inputDataset.size() == 1)
    1184             :                     {
    1185          15 :                         auto poDS = step.alg->m_inputDataset[0].GetDatasetRef();
    1186          15 :                         if (poDS && poDS->GetLayerCount() > 0)
    1187           9 :                             ret = false;
    1188             :                     }
    1189           3 :                     else if (!ret && firstStep)
    1190           3 :                         ret = true;
    1191         403 :                 }
    1192             :             }
    1193          32 :             else if (!m_bExpectReadStep &&
    1194           8 :                      nDatasetType == step.alg->GetInputType())
    1195             :             {
    1196           5 :                 step.alg->m_skipValidationInParseCommandLine = true;
    1197           5 :                 ret = step.alg->ParseCommandLineArguments(step.args);
    1198           5 :                 if (!ret)
    1199           1 :                     return false;
    1200             :             }
    1201             : 
    1202         426 :             if (!ret)
    1203             :             {
    1204             :                 auto algVector =
    1205          44 :                     GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
    1206          82 :                 if (algVector &&
    1207          82 :                     (nDatasetType == 0 || nDatasetType == GDAL_OF_VECTOR))
    1208             :                 {
    1209          32 :                     step.alg = std::move(algVector);
    1210          32 :                     step.alg->m_inputDatasetCanBeOmitted =
    1211          32 :                         !firstStep || !m_bExpectReadStep;
    1212          32 :                     step.alg->m_skipValidationInParseCommandLine = true;
    1213          32 :                     ret = step.alg->ParseCommandLineArguments(step.args);
    1214          32 :                     if (ret)
    1215             :                     {
    1216          38 :                         step.alg->SetCallPath({step.alg->GetName()});
    1217          19 :                         step.alg->SetReferencePathForRelativePaths(
    1218             :                             GetReferencePathForRelativePaths());
    1219          19 :                         step.alreadyChangedType = true;
    1220             :                     }
    1221          13 :                     else if (!forAutoComplete)
    1222           6 :                         return false;
    1223             :                 }
    1224          38 :                 if (!ret && hasTriedRaster && !forAutoComplete)
    1225             :                 {
    1226           2 :                     for (const auto &sError : oAccumulator.GetErrors())
    1227             :                     {
    1228           1 :                         CPLError(sError.type, sError.no, "%s",
    1229             :                                  sError.msg.c_str());
    1230             :                     }
    1231           1 :                     return false;
    1232             :                 }
    1233             :             }
    1234         419 :             if (ret && forAutoComplete)
    1235          28 :                 nDatasetType = step.alg->GetOutputType();
    1236         419 :             firstStep = false;
    1237             :         }
    1238             :     }
    1239             :     else
    1240             :     {
    1241         804 :         for (auto &step : steps)
    1242             :         {
    1243         573 :             step.alg->m_skipValidationInParseCommandLine = true;
    1244         593 :             if (!step.alg->ParseCommandLineArguments(step.args) &&
    1245          20 :                 !forAutoComplete)
    1246          15 :                 return false;
    1247             :         }
    1248             :     }
    1249             : 
    1250             :     // Evaluate "input" argument of "read" step, together with the "output"
    1251             :     // argument of the "write" step, in case they point to the same dataset.
    1252         408 :     auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
    1253         791 :     if (inputArg && inputArg->IsExplicitlySet() &&
    1254        1199 :         inputArg->GetType() == GAAT_DATASET_LIST &&
    1255         383 :         inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
    1256             :     {
    1257         378 :         int nCountChangeFieldTypeStepsToBeRemoved = 0;
    1258         378 :         std::string osTmpJSONFilename;
    1259             : 
    1260             :         // Check if there are steps like change-field-type just after the read
    1261             :         // step. If so, we can convert them into a OGR_SCHEMA open option for
    1262             :         // drivers that support it.
    1263         378 :         auto &inputVals = inputArg->Get<std::vector<GDALArgDatasetValue>>();
    1264        1014 :         if (!inputVals[0].GetDatasetRef() && steps.size() >= 2 &&
    1265        1014 :             steps[0].alg->GetName() == GDALVectorReadAlgorithm::NAME &&
    1266         292 :             !steps.back().alg->IsGDALGOutput())
    1267             :         {
    1268             :             auto openOptionArgs =
    1269         280 :                 steps.front().alg->GetArg(GDAL_ARG_NAME_OPEN_OPTION);
    1270         560 :             if (openOptionArgs && !openOptionArgs->IsExplicitlySet() &&
    1271         280 :                 openOptionArgs->GetType() == GAAT_STRING_LIST)
    1272             :             {
    1273             :                 const auto &openOptionVals =
    1274         280 :                     openOptionArgs->Get<std::vector<std::string>>();
    1275         560 :                 if (CPLStringList(openOptionVals)
    1276         280 :                         .FetchNameValue("OGR_SCHEMA") == nullptr)
    1277             :                 {
    1278         560 :                     CPLJSONArray oLayers;
    1279         289 :                     for (size_t iStep = 1; iStep < steps.size(); ++iStep)
    1280             :                     {
    1281             :                         auto oObj =
    1282         286 :                             steps[iStep].alg->Get_OGR_SCHEMA_OpenOption_Layer();
    1283         286 :                         if (!oObj.IsValid())
    1284         277 :                             break;
    1285           9 :                         oLayers.Add(oObj);
    1286           9 :                         ++nCountChangeFieldTypeStepsToBeRemoved;
    1287             :                     }
    1288             : 
    1289         280 :                     if (nCountChangeFieldTypeStepsToBeRemoved > 0)
    1290             :                     {
    1291           7 :                         CPLJSONDocument oDoc;
    1292           7 :                         oDoc.GetRoot().Set("layers", oLayers);
    1293             :                         osTmpJSONFilename =
    1294           7 :                             VSIMemGenerateHiddenFilename(nullptr);
    1295             :                         // CPLDebug("GDAL", "OGR_SCHEMA: %s", oDoc.SaveAsString().c_str());
    1296           7 :                         oDoc.Save(osTmpJSONFilename);
    1297             : 
    1298          14 :                         openOptionArgs->Set(std::vector<std::string>{
    1299          14 :                             std::string("@OGR_SCHEMA=")
    1300          14 :                                 .append(osTmpJSONFilename)});
    1301             :                     }
    1302             :                 }
    1303             :             }
    1304             :         }
    1305             : 
    1306         756 :         const bool bOK = steps.front().alg->ProcessDatasetArg(
    1307         756 :                              inputArg, steps.back().alg.get()) ||
    1308         378 :                          forAutoComplete;
    1309             : 
    1310         378 :         if (!osTmpJSONFilename.empty())
    1311           7 :             VSIUnlink(osTmpJSONFilename.c_str());
    1312             : 
    1313         378 :         if (!bOK)
    1314             :         {
    1315           7 :             return false;
    1316             :         }
    1317             : 
    1318             :         // Now check if the driver of the input dataset actually supports
    1319             :         // the OGR_SCHEMA open option. If so, we can remove the steps from
    1320             :         // the pipeline
    1321         371 :         if (nCountChangeFieldTypeStepsToBeRemoved)
    1322             :         {
    1323           7 :             if (auto poDS = inputVals[0].GetDatasetRef())
    1324             :             {
    1325           7 :                 if (auto poDriver = poDS->GetDriver())
    1326             :                 {
    1327             :                     const char *pszOpenOptionList =
    1328           7 :                         poDriver->GetMetadataItem(GDAL_DMD_OPENOPTIONLIST);
    1329           7 :                     if (pszOpenOptionList &&
    1330           7 :                         strstr(pszOpenOptionList, "OGR_SCHEMA"))
    1331             :                     {
    1332           1 :                         CPLDebug("GDAL",
    1333             :                                  "Merging %d step(s) as OGR_SCHEMA open option",
    1334             :                                  nCountChangeFieldTypeStepsToBeRemoved);
    1335           1 :                         steps.erase(steps.begin() + 1,
    1336           1 :                                     steps.begin() + 1 +
    1337           3 :                                         nCountChangeFieldTypeStepsToBeRemoved);
    1338             :                     }
    1339             :                 }
    1340             :             }
    1341             :         }
    1342             :     }
    1343             : 
    1344         401 :     if (bIsGenericPipeline)
    1345             :     {
    1346         176 :         int nLastStepOutputType = nInitialDatasetType;
    1347         176 :         if (m_bExpectReadStep)
    1348             :         {
    1349         158 :             nLastStepOutputType = GDAL_OF_VECTOR;
    1350         158 :             if (steps.front().alg->GetName() !=
    1351         321 :                     std::string(GDALRasterReadAlgorithm::NAME) &&
    1352           5 :                 steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
    1353             :             {
    1354           1 :                 nLastStepOutputType = GDAL_OF_RASTER;
    1355             :             }
    1356             :             else
    1357             :             {
    1358         157 :                 auto &inputDatasets = steps.front().alg->GetInputDatasets();
    1359         157 :                 if (!inputDatasets.empty())
    1360             :                 {
    1361         152 :                     auto poSrcDS = inputDatasets[0].GetDatasetRef();
    1362         152 :                     if (poSrcDS)
    1363             :                     {
    1364         149 :                         if (poSrcDS->GetRasterCount() != 0)
    1365          95 :                             nLastStepOutputType = GDAL_OF_RASTER;
    1366             :                     }
    1367             :                 }
    1368             :             }
    1369             :         }
    1370             : 
    1371         168 :         for (size_t i =
    1372         158 :                  ((m_bExpectReadStep && steps[0].alg->GetOutputType() != 0)
    1373         334 :                       ? 1
    1374         176 :                       : 0);
    1375         344 :              !forAutoComplete && i < steps.size(); ++i)
    1376             :         {
    1377         198 :             auto &step = steps[i];
    1378             : 
    1379         389 :             if (!step.alreadyChangedType && !step.isSubAlgorithm &&
    1380         389 :                 GetStepAlg(step.alg->GetName()) == nullptr)
    1381             :             {
    1382             :                 // GetInputDatasetType() is to deal with pipelines like
    1383             :                 // gdal pipeline read input_vector ! clip --input raster_dataset --like _PIPE_ ! write output_raster
    1384             :                 const int nThisDatasetType =
    1385         117 :                     GetInputDatasetType(step.alg.get());
    1386         117 :                 const int nThisStepType =
    1387         117 :                     nThisDatasetType ? nThisDatasetType : nLastStepOutputType;
    1388             : 
    1389         234 :                 if (step.alg->GetInputType() != 0 &&
    1390         117 :                     nThisStepType != step.alg->GetInputType())
    1391             :                 {
    1392          27 :                     auto newAlg = GetStepAlg(step.alg->GetName() +
    1393             :                                              (nThisStepType == GDAL_OF_RASTER
    1394             :                                                   ? RASTER_SUFFIX
    1395          27 :                                                   : VECTOR_SUFFIX));
    1396          27 :                     CPLAssert(newAlg);
    1397             : 
    1398             :                     const bool maybeWriteStep =
    1399          47 :                         (i == steps.size() - 1 &&
    1400          20 :                          m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE);
    1401             : 
    1402          27 :                     if (!CopyStepAlgorithmFromAnother(
    1403          27 :                             newAlg.get(), step.alg.get(), maybeWriteStep))
    1404           1 :                         return false;
    1405             : 
    1406          26 :                     newAlg->m_inputDatasetCanBeOmitted =
    1407          26 :                         i > 0 || !m_bExpectReadStep;
    1408          26 :                     step.alg = std::move(newAlg);
    1409          26 :                     step.alreadyChangedType = true;
    1410             :                 }
    1411             :             }
    1412             : 
    1413         197 :             if (i > 0)
    1414             :             {
    1415             :                 bool emitError =
    1416         332 :                     (step.alg->GetInputType() != 0 &&
    1417         157 :                      step.alg->GetInputType() != nLastStepOutputType);
    1418             : 
    1419             :                 // Check if a dataset argument, which has as value the
    1420             :                 // placeholder value, has the same dataset type as the output
    1421             :                 // of the last step
    1422        2529 :                 for (const auto &arg : step.alg->GetArgs())
    1423             :                 {
    1424        6891 :                     if (!arg->IsOutput() &&
    1425        4527 :                         (arg->GetType() == GAAT_DATASET ||
    1426        2248 :                          arg->GetType() == GAAT_DATASET_LIST))
    1427             :                     {
    1428         225 :                         if (arg->GetType() == GAAT_DATASET)
    1429             :                         {
    1430          31 :                             if (arg->Get<GDALArgDatasetValue>().GetName() ==
    1431             :                                 GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
    1432             :                             {
    1433           8 :                                 if ((arg->GetDatasetType() &
    1434           8 :                                      nLastStepOutputType) != 0)
    1435             :                                 {
    1436           8 :                                     emitError = false;
    1437           8 :                                     break;
    1438             :                                 }
    1439             :                             }
    1440             :                         }
    1441             :                         else
    1442             :                         {
    1443         194 :                             CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
    1444             :                             auto &val =
    1445         194 :                                 arg->Get<std::vector<GDALArgDatasetValue>>();
    1446         218 :                             if (val.size() == 1 &&
    1447          24 :                                 val[0].GetName() ==
    1448             :                                     GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
    1449             :                             {
    1450           3 :                                 if ((arg->GetDatasetType() &
    1451           3 :                                      nLastStepOutputType) != 0)
    1452             :                                 {
    1453           2 :                                     emitError = false;
    1454           2 :                                     break;
    1455             :                                 }
    1456             :                             }
    1457             :                         }
    1458             :                     }
    1459             :                 }
    1460         175 :                 if (emitError)
    1461             :                 {
    1462          12 :                     ReportError(
    1463             :                         CE_Failure, CPLE_AppDefined,
    1464             :                         "Step '%s' expects a %s input dataset, but "
    1465             :                         "previous step '%s' "
    1466             :                         "generates a %s output dataset",
    1467           3 :                         step.alg->GetName().c_str(),
    1468           3 :                         step.alg->GetInputType() == GDAL_OF_RASTER ? "raster"
    1469           1 :                         : step.alg->GetInputType() == GDAL_OF_VECTOR
    1470           1 :                             ? "vector"
    1471             :                             : "unknown",
    1472           3 :                         steps[i - 1].alg->GetName().c_str(),
    1473           3 :                         nLastStepOutputType == GDAL_OF_RASTER   ? "raster"
    1474           2 :                         : nLastStepOutputType == GDAL_OF_VECTOR ? "vector"
    1475             :                                                                 : "unknown");
    1476           3 :                     return false;
    1477             :                 }
    1478             :             }
    1479             : 
    1480         194 :             nLastStepOutputType = step.alg->GetOutputType();
    1481         194 :             if (!forAutoComplete && nLastStepOutputType == 0)
    1482             :             {
    1483             :                 // If this step has no precise output dataset (unique instance
    1484             :                 // at time of writing is 'external'), we stop trying to fix
    1485             :                 // the raster/vector nature of ambiguous steps for now, and
    1486             :                 // defer doing that during pipeline execution itself.
    1487          26 :                 m_nFirstStepWithUnknownInputType = static_cast<int>(i + 1);
    1488          26 :                 break;
    1489             :             }
    1490             :         }
    1491             :     }
    1492             : 
    1493         397 :     int iStep = 0;
    1494        1289 :     for (const auto &step : steps)
    1495             :     {
    1496         921 :         if (iStep == m_nFirstStepWithUnknownInputType)
    1497          20 :             break;
    1498         901 :         if (!step.alg->ValidateArguments() && !forAutoComplete)
    1499           9 :             return false;
    1500         892 :         ++iStep;
    1501             :     }
    1502             : 
    1503        1300 :     for (auto &step : steps)
    1504         912 :         m_steps.push_back(std::move(step.alg));
    1505             : 
    1506         388 :     return true;
    1507             : }
    1508             : 
    1509             : /************************************************************************/
    1510             : /*         GDALAbstractPipelineAlgorithm::BuildNestedPipeline()         */
    1511             : /************************************************************************/
    1512             : 
    1513             : /** Build a nested pipeline
    1514             :  *
    1515             :  * @param curAlg Current algorithm for which the nested pipeline will be a child.
    1516             :  *               e.g in "gdal pipeline read ... ! clip --like [ ... ]",
    1517             :  *               curAlg is "clip".
    1518             :  * @param nestedPipelineArgs Arguments of the nested pipeline, i.e. values
    1519             :  *                           between square brackets.
    1520             :  * @param forAutoComplete true if this method is called from GetAutoComplete()
    1521             :  * @param[out] pCurArgsForAutocomplete Pointer to a vector of string, or null.
    1522             :  *                                     If provided, it will contain the arguments
    1523             :  *                                     of the active pipeline. Useful for
    1524             :  *                                     completion in nested pipelines.
    1525             :  */
    1526          53 : std::string GDALAbstractPipelineAlgorithm::BuildNestedPipeline(
    1527             :     GDALPipelineStepAlgorithm *curAlg,
    1528             :     std::vector<std::string> &nestedPipelineArgs, bool forAutoComplete,
    1529             :     std::vector<std::string> *pCurArgsForAutocomplete)
    1530             : {
    1531          53 :     std::string datasetNameOut;
    1532          53 :     CPLAssert(curAlg);
    1533             : 
    1534         106 :     auto nestedPipeline = CreateNestedPipeline();
    1535          53 :     if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
    1536          27 :         nestedPipeline->m_bExpectReadStep = false;
    1537             :     else
    1538          26 :         nestedPipeline->m_eLastStepAsWrite = StepConstraint::CAN_NOT_BE;
    1539          53 :     nestedPipeline->m_executionForStreamOutput = m_executionForStreamOutput;
    1540          53 :     if (IsCalledFromCommandLine())
    1541           6 :         nestedPipeline->SetCalledFromCommandLine();
    1542          53 :     nestedPipeline->SetReferencePathForRelativePaths(
    1543             :         GetReferencePathForRelativePaths());
    1544             : 
    1545         106 :     std::string argsStr = OPEN_NESTED_PIPELINE;
    1546         208 :     for (const std::string &str : nestedPipelineArgs)
    1547             :     {
    1548         155 :         argsStr += ' ';
    1549         155 :         argsStr += GDALAlgorithmArg::GetEscapedString(str);
    1550             :     }
    1551          53 :     argsStr += ' ';
    1552          53 :     argsStr += CLOSE_NESTED_PIPELINE;
    1553             : 
    1554          53 :     if (curAlg->GetName() != GDALTeeStepAlgorithmAbstract::NAME)
    1555             :     {
    1556          26 :         if (!nestedPipeline->ParseCommandLineArguments(
    1557          47 :                 nestedPipelineArgs, forAutoComplete, pCurArgsForAutocomplete) ||
    1558          21 :             (!forAutoComplete && !nestedPipeline->Run()))
    1559             :         {
    1560           6 :             return datasetNameOut;
    1561             :         }
    1562          20 :         auto poDS = nestedPipeline->GetOutputDataset().GetDatasetRef();
    1563          20 :         if (!poDS)
    1564             :         {
    1565             :             // That shouldn't happen normally for well-behaved algorithms, but
    1566             :             // it doesn't hurt checking.
    1567           2 :             ReportError(CE_Failure, CPLE_AppDefined,
    1568             :                         "Nested pipeline does not generate an output dataset");
    1569           2 :             return datasetNameOut;
    1570             :         }
    1571             :         datasetNameOut =
    1572          18 :             CPLSPrintf("$$nested_pipeline_%p$$", nestedPipeline.get());
    1573          18 :         curAlg->m_oMapDatasetNameToDataset[datasetNameOut] = poDS;
    1574             : 
    1575          18 :         poDS->SetDescription(argsStr.c_str());
    1576          18 :         auto poVRTDataset = dynamic_cast<VRTDataset *>(poDS);
    1577          18 :         if (poVRTDataset)
    1578           2 :             poVRTDataset->SetWritable(false);
    1579             :     }
    1580             : 
    1581          45 :     m_apoNestedPipelines.emplace_back(std::move(nestedPipeline));
    1582             : 
    1583          45 :     if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
    1584             :     {
    1585          27 :         auto teeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(curAlg);
    1586          27 :         if (teeAlg)
    1587             :         {
    1588          27 :             datasetNameOut = std::move(argsStr);
    1589          27 :             if (!teeAlg->BindFilename(datasetNameOut,
    1590          27 :                                       m_apoNestedPipelines.back().get(),
    1591             :                                       nestedPipelineArgs))
    1592             :             {
    1593           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
    1594             :                             "Another identical nested pipeline exists");
    1595           1 :                 datasetNameOut.clear();
    1596             :             }
    1597             :         }
    1598             :     }
    1599             : 
    1600          45 :     nestedPipelineArgs.clear();
    1601             : 
    1602          45 :     return datasetNameOut;
    1603             : }
    1604             : 
    1605             : /************************************************************************/
    1606             : /*           GDALAbstractPipelineAlgorithm::GetAutoComplete()           */
    1607             : /************************************************************************/
    1608             : 
    1609             : std::vector<std::string>
    1610          54 : GDALAbstractPipelineAlgorithm::GetAutoComplete(std::vector<std::string> &argsIn,
    1611             :                                                bool lastWordIsComplete,
    1612             :                                                bool showAllOptions)
    1613             : {
    1614         108 :     std::vector<std::string> args;
    1615             :     {
    1616         108 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    1617          54 :         ParseCommandLineArguments(argsIn, /*forAutoComplete=*/true, &args);
    1618             :     }
    1619             :     VSIStatBufL sStat;
    1620          60 :     if (!m_pipeline.empty() && VSIStatL(m_pipeline.c_str(), &sStat) == 0 &&
    1621          60 :         !m_steps.empty() && !args.empty())
    1622             :     {
    1623          12 :         std::map<std::string, std::vector<GDALAlgorithm *>> mapSteps;
    1624          26 :         for (const auto &step : m_steps)
    1625             :         {
    1626          20 :             mapSteps[step->GetName()].push_back(step.get());
    1627             :         }
    1628             : 
    1629          12 :         std::vector<std::string> ret;
    1630           6 :         const auto &lastArg = args.back();
    1631          18 :         if (!lastArg.empty() && lastArg[0] == '-' &&
    1632          18 :             lastArg.find('=') == std::string::npos && !lastWordIsComplete)
    1633             :         {
    1634          13 :             for (const auto &step : m_steps)
    1635             :             {
    1636             :                 const int iterCount =
    1637          10 :                     static_cast<int>(mapSteps[step->GetName()].size());
    1638          22 :                 for (int i = 0; i < iterCount; ++i)
    1639             :                 {
    1640         171 :                     for (const auto &arg : step->GetArgs())
    1641             :                     {
    1642         297 :                         if (!arg->IsHiddenForCLI() &&
    1643         138 :                             arg->GetCategory() != GAAC_COMMON)
    1644             :                         {
    1645         200 :                             std::string s = std::string("--");
    1646         200 :                             if (!((step->GetName() ==
    1647          11 :                                        GDALRasterReadAlgorithm::NAME &&
    1648          11 :                                    IsReadSpecificArgument(
    1649          11 :                                        arg->GetName().c_str())) ||
    1650          89 :                                   (step->GetName() ==
    1651          31 :                                        GDALRasterWriteAlgorithm::NAME &&
    1652          31 :                                    IsWriteSpecificArgument(
    1653          31 :                                        arg->GetName().c_str()))))
    1654             :                             {
    1655          66 :                                 s += step->GetName();
    1656          66 :                                 if (iterCount > 1)
    1657             :                                 {
    1658          36 :                                     s += '[';
    1659          36 :                                     s += std::to_string(i);
    1660          36 :                                     s += ']';
    1661             :                                 }
    1662          66 :                                 s += '.';
    1663             :                             }
    1664         100 :                             s += arg->GetName();
    1665         100 :                             if (arg->GetType() == GAAT_BOOLEAN)
    1666          22 :                                 ret.push_back(std::move(s));
    1667             :                             else
    1668          78 :                                 ret.push_back(s + "=");
    1669             :                         }
    1670             :                     }
    1671             :                 }
    1672             :             }
    1673             :         }
    1674           6 :         else if (cpl::starts_with(lastArg, "--") &&
    1675           6 :                  lastArg.find('=') != std::string::npos && !lastWordIsComplete)
    1676             :         {
    1677           3 :             const auto nDotPos = lastArg.find('.');
    1678           6 :             std::string stepName;
    1679           6 :             std::string argName;
    1680           3 :             int idx = 0;
    1681           3 :             if (nDotPos != std::string::npos)
    1682             :             {
    1683           1 :                 stepName = lastArg.substr(strlen("--"), nDotPos - strlen("--"));
    1684           1 :                 const auto nBracketPos = stepName.find('[');
    1685           1 :                 if (nBracketPos != std::string::npos)
    1686             :                 {
    1687           1 :                     idx = atoi(stepName.c_str() + nBracketPos + 1);
    1688           1 :                     stepName.resize(nBracketPos);
    1689             :                 }
    1690           1 :                 argName = "--" + lastArg.substr(nDotPos + 1);
    1691             :             }
    1692             :             else
    1693             :             {
    1694           2 :                 argName = lastArg;
    1695           7 :                 for (const char *prefix : apszReadParametersPrefixOmitted)
    1696             :                 {
    1697           6 :                     if (cpl::starts_with(lastArg.substr(strlen("--")),
    1698          12 :                                          std::string(prefix) + "="))
    1699             :                     {
    1700           1 :                         stepName = GDALRasterReadAlgorithm::NAME;
    1701           1 :                         break;
    1702             :                     }
    1703             :                 }
    1704             : 
    1705          13 :                 for (const char *prefix : apszWriteParametersPrefixOmitted)
    1706             :                 {
    1707          12 :                     if (cpl::starts_with(lastArg.substr(strlen("--")),
    1708          24 :                                          std::string(prefix) + "="))
    1709             :                     {
    1710           1 :                         stepName = GDALRasterWriteAlgorithm::NAME;
    1711           1 :                         break;
    1712             :                     }
    1713             :                 }
    1714             :             }
    1715             : 
    1716           3 :             auto iter = mapSteps.find(stepName);
    1717           6 :             if (iter != mapSteps.end() && idx >= 0 &&
    1718           3 :                 static_cast<size_t>(idx) < iter->second.size())
    1719             :             {
    1720           3 :                 auto &step = iter->second[idx];
    1721           3 :                 std::vector<std::string> subArgs;
    1722          34 :                 for (const auto &arg : step->GetArgs())
    1723             :                 {
    1724          62 :                     std::string strArg;
    1725          35 :                     if (arg->IsExplicitlySet() &&
    1726           4 :                         arg->Serialize(strArg, /* absolutePath=*/false))
    1727             :                     {
    1728           4 :                         subArgs.push_back(std::move(strArg));
    1729             :                     }
    1730             :                 }
    1731           3 :                 subArgs.push_back(std::move(argName));
    1732           3 :                 ret = step->GetAutoComplete(subArgs, lastWordIsComplete,
    1733           3 :                                             showAllOptions);
    1734             :             }
    1735             :         }
    1736           6 :         return ret;
    1737             :     }
    1738             :     else
    1739             :     {
    1740          96 :         std::vector<std::string> ret;
    1741          96 :         std::set<std::string> setSuggestions;
    1742          48 :         if (args.size() <= 1)
    1743             :         {
    1744         433 :             for (const std::string &name : GetStepRegistry().GetNames())
    1745             :             {
    1746         427 :                 auto alg = GetStepRegistry().Instantiate(name);
    1747             :                 auto stepAlg =
    1748         427 :                     dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
    1749         427 :                 if (stepAlg && stepAlg->CanBeFirstStep())
    1750             :                 {
    1751             :                     std::string suggestionName =
    1752          54 :                         CPLString(name)
    1753         108 :                             .replaceAll(RASTER_SUFFIX, "")
    1754         108 :                             .replaceAll(VECTOR_SUFFIX, "");
    1755          54 :                     if (!cpl::contains(setSuggestions, suggestionName))
    1756             :                     {
    1757          51 :                         if (!args.empty() && suggestionName == args[0])
    1758           3 :                             return {};
    1759          79 :                         if (args.empty() ||
    1760          31 :                             cpl::starts_with(suggestionName, args[0]))
    1761             :                         {
    1762          20 :                             setSuggestions.insert(suggestionName);
    1763          20 :                             ret.push_back(std::move(suggestionName));
    1764             :                         }
    1765             :                     }
    1766             :                 }
    1767             :             }
    1768             :         }
    1769             :         else
    1770             :         {
    1771          39 :             int nDatasetType = GetInputType();
    1772          39 :             constexpr int MIXED_TYPE = GDAL_OF_RASTER | GDAL_OF_VECTOR;
    1773          39 :             const bool isMixedTypePipeline = nDatasetType == MIXED_TYPE;
    1774          39 :             std::string lastStep = args[0];
    1775          39 :             std::vector<std::string> lastArgs;
    1776          39 :             bool firstStep = true;
    1777          39 :             bool foundSlowStep = false;
    1778         146 :             for (size_t i = 1; i < args.size(); ++i)
    1779             :             {
    1780          67 :                 if (firstStep && isMixedTypePipeline &&
    1781         199 :                     nDatasetType == MIXED_TYPE && !args[i].empty() &&
    1782          25 :                     args[i][0] != '-')
    1783             :                 {
    1784          44 :                     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    1785             :                     auto poDS = std::unique_ptr<GDALDataset>(
    1786          44 :                         GDALDataset::Open(args[i].c_str()));
    1787          22 :                     if (poDS)
    1788             :                     {
    1789          11 :                         const int nThisDatasetType = GetDatasetType(poDS.get());
    1790          11 :                         if (nThisDatasetType)
    1791          11 :                             nDatasetType = nThisDatasetType;
    1792             :                     }
    1793             :                 }
    1794         107 :                 lastArgs.push_back(args[i]);
    1795         107 :                 if (i + 1 < args.size() && args[i] == "!")
    1796             :                 {
    1797          28 :                     firstStep = false;
    1798          28 :                     ++i;
    1799          28 :                     lastArgs.clear();
    1800          28 :                     lastStep = args[i];
    1801          56 :                     auto curAlg = GetStepAlg(lastStep);
    1802          28 :                     if (isMixedTypePipeline && !curAlg)
    1803             :                     {
    1804          11 :                         if (nDatasetType == GDAL_OF_RASTER)
    1805           2 :                             curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
    1806           9 :                         else if (nDatasetType == GDAL_OF_VECTOR)
    1807           3 :                             curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
    1808             :                     }
    1809          28 :                     if (curAlg)
    1810             :                     {
    1811          19 :                         foundSlowStep =
    1812          36 :                             foundSlowStep ||
    1813          17 :                             !curAlg->IsNativelyStreamingCompatible();
    1814          19 :                         nDatasetType = curAlg->GetOutputType();
    1815             :                     }
    1816             :                 }
    1817             :             }
    1818             : 
    1819          73 :             if (args.back() == "!" ||
    1820          73 :                 (args[args.size() - 2] == "!" && !GetStepAlg(args.back()) &&
    1821          42 :                  !GetStepAlg(args.back() + RASTER_SUFFIX) &&
    1822          42 :                  !GetStepAlg(args.back() + VECTOR_SUFFIX)))
    1823             :             {
    1824         631 :                 for (const std::string &name : GetStepRegistry().GetNames())
    1825             :                 {
    1826         620 :                     auto alg = GetStepRegistry().Instantiate(name);
    1827             :                     auto stepAlg =
    1828         620 :                         dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
    1829         620 :                     if (stepAlg && isMixedTypePipeline &&
    1830        1240 :                         nDatasetType != MIXED_TYPE &&
    1831         172 :                         stepAlg->GetInputType() != nDatasetType)
    1832             :                     {
    1833          87 :                         continue;
    1834             :                     }
    1835         533 :                     if (stepAlg && !stepAlg->CanBeFirstStep())
    1836             :                     {
    1837             :                         std::string suggestionName =
    1838         473 :                             CPLString(name)
    1839         946 :                                 .replaceAll(RASTER_SUFFIX, "")
    1840        1419 :                                 .replaceAll(VECTOR_SUFFIX, "");
    1841         473 :                         if (!cpl::contains(setSuggestions, suggestionName))
    1842             :                         {
    1843         455 :                             setSuggestions.insert(suggestionName);
    1844         455 :                             ret.push_back(std::move(suggestionName));
    1845             :                         }
    1846             :                     }
    1847             :                 }
    1848             :             }
    1849             :             else
    1850             :             {
    1851          28 :                 if (!foundSlowStep)
    1852             :                 {
    1853             :                     // Try to run the pipeline so that the last step gets its
    1854             :                     // input dataset.
    1855          24 :                     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    1856          24 :                     GDALPipelineStepRunContext ctxt;
    1857          24 :                     RunStep(ctxt);
    1858          38 :                     if (!m_steps.empty() &&
    1859          14 :                         m_steps.back()->GetName() == lastStep)
    1860             :                     {
    1861          14 :                         return m_steps.back()->GetAutoComplete(
    1862             :                             lastArgs, lastWordIsComplete,
    1863          14 :                             /* showAllOptions = */ false);
    1864             :                     }
    1865             :                 }
    1866             : 
    1867          28 :                 auto curAlg = GetStepAlg(lastStep);
    1868          14 :                 if (isMixedTypePipeline && !curAlg)
    1869             :                 {
    1870           5 :                     if (nDatasetType == GDAL_OF_RASTER)
    1871           1 :                         curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
    1872           4 :                     else if (nDatasetType == GDAL_OF_VECTOR)
    1873           0 :                         curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
    1874             :                     else
    1875             :                     {
    1876           8 :                         for (const char *suffix :
    1877          12 :                              {RASTER_SUFFIX, VECTOR_SUFFIX})
    1878             :                         {
    1879           8 :                             curAlg = GetStepAlg(lastStep + suffix);
    1880           8 :                             if (curAlg)
    1881             :                             {
    1882          48 :                                 for (const auto &v : curAlg->GetAutoComplete(
    1883             :                                          lastArgs, lastWordIsComplete,
    1884          88 :                                          /* showAllOptions = */ false))
    1885             :                                 {
    1886          40 :                                     if (!cpl::contains(setSuggestions, v))
    1887             :                                     {
    1888          29 :                                         setSuggestions.insert(v);
    1889          29 :                                         ret.push_back(std::move(v));
    1890             :                                     }
    1891             :                                 }
    1892             :                             }
    1893             :                         }
    1894           4 :                         curAlg.reset();
    1895             :                     }
    1896             :                 }
    1897          14 :                 if (curAlg)
    1898             :                 {
    1899          20 :                     ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
    1900          10 :                                                   /* showAllOptions = */ false);
    1901             :                 }
    1902             :             }
    1903             :         }
    1904          31 :         return ret;
    1905             :     }
    1906             : }
    1907             : 
    1908             : /************************************************************************/
    1909             : /*      GDALAbstractPipelineAlgorithm::SaveGDALGIntoFileOrString()      */
    1910             : /************************************************************************/
    1911             : 
    1912             : /** Save the pipeline either into the file of name outFilename, if
    1913             :  * outFilename is not empty, or into the output string outString if
    1914             :  * outFilename is empty.
    1915             :  */
    1916          13 : bool GDALAbstractPipelineAlgorithm::SaveGDALGIntoFileOrString(
    1917             :     const std::string &outFilename, std::string &outString) const
    1918             : {
    1919          26 :     std::string osCommandLine;
    1920             : 
    1921          48 :     for (const auto &path : GDALAlgorithm::m_callPath)
    1922             :     {
    1923          35 :         if (!osCommandLine.empty())
    1924          22 :             osCommandLine += ' ';
    1925          35 :         osCommandLine += path;
    1926             :     }
    1927             : 
    1928             :     // Do not include the last step
    1929          35 :     for (size_t i = 0; i + 1 < m_steps.size(); ++i)
    1930             :     {
    1931          23 :         const auto &step = m_steps[i];
    1932          23 :         if (!step->IsNativelyStreamingCompatible())
    1933             :         {
    1934           3 :             GDALAlgorithm::ReportError(
    1935             :                 CE_Warning, CPLE_AppDefined,
    1936             :                 "Step %s is not natively streaming compatible, and "
    1937             :                 "may cause significant processing time at opening",
    1938           3 :                 step->GDALAlgorithm::GetName().c_str());
    1939             :         }
    1940             : 
    1941          23 :         if (i > 0)
    1942          10 :             osCommandLine += " !";
    1943          46 :         for (const auto &path : step->GDALAlgorithm::m_callPath)
    1944             :         {
    1945          23 :             if (!osCommandLine.empty())
    1946          23 :                 osCommandLine += ' ';
    1947          23 :             osCommandLine += path;
    1948             :         }
    1949             : 
    1950         246 :         for (const auto &arg : step->GetArgs())
    1951             :         {
    1952         224 :             if (arg->IsExplicitlySet())
    1953             :             {
    1954          22 :                 osCommandLine += ' ';
    1955          22 :                 std::string strArg;
    1956          22 :                 if (!arg->Serialize(strArg, /* absolutePath=*/false))
    1957             :                 {
    1958           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1959             :                              "Cannot serialize argument %s",
    1960           1 :                              arg->GetName().c_str());
    1961           1 :                     return false;
    1962             :                 }
    1963          21 :                 osCommandLine += strArg;
    1964             :             }
    1965             :         }
    1966             :     }
    1967             : 
    1968          12 :     return GDALAlgorithm::SaveGDALG(outFilename, outString, osCommandLine);
    1969             : }
    1970             : 
    1971             : /************************************************************************/
    1972             : /*                      RunStepDealWithGDALGJson()                      */
    1973             : /************************************************************************/
    1974             : 
    1975             : GDALAbstractPipelineAlgorithm::RunStepState
    1976         375 : GDALAbstractPipelineAlgorithm::RunStepDealWithGDALGJson()
    1977             : {
    1978             :     // Handle output to GDALG file
    1979         750 :     if (!m_steps.empty() &&
    1980         375 :         m_steps.back()->GetName() == GDALRasterWriteAlgorithm::NAME)
    1981             :     {
    1982         220 :         const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
    1983             :         const auto outputFormatArg =
    1984         220 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
    1985         440 :         if (outputArg && outputArg->GetType() == GAAT_DATASET &&
    1986         220 :             outputArg->IsExplicitlySet())
    1987             :         {
    1988             :             const std::string &outputFileName =
    1989         220 :                 outputArg->Get<GDALArgDatasetValue>().GetName();
    1990         220 :             if (m_steps.back()->IsGDALGOutput())
    1991             :             {
    1992          12 :                 std::string outStringUnused;
    1993          12 :                 return SaveGDALGIntoFileOrString(outputFileName,
    1994             :                                                  outStringUnused)
    1995          12 :                            ? RunStepState::PROCESSED
    1996          12 :                            : RunStepState::ERROR;
    1997             :             }
    1998             : 
    1999             :             bool isVRTOutput;
    2000         416 :             if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
    2001         208 :                 outputFormatArg->IsExplicitlySet())
    2002             :             {
    2003          80 :                 const auto &val = outputFormatArg->Get<std::string>();
    2004          80 :                 isVRTOutput = EQUAL(val.c_str(), "vrt");
    2005             :             }
    2006             :             else
    2007             :             {
    2008         128 :                 isVRTOutput = EQUAL(
    2009             :                     CPLGetExtensionSafe(outputFileName.c_str()).c_str(), "vrt");
    2010             :             }
    2011         208 :             if (isVRTOutput && !outputFileName.empty() && m_steps.size() > 3)
    2012             :             {
    2013           1 :                 ReportError(
    2014             :                     CE_Failure, CPLE_NotSupported,
    2015             :                     "VRT output is not supported when there are more than 3 "
    2016             :                     "steps. Consider using the GDALG driver (files with "
    2017             :                     ".gdalg.json extension)");
    2018           1 :                 return RunStepState::ERROR;
    2019             :             }
    2020         207 :             if (isVRTOutput)
    2021             :             {
    2022          31 :                 for (const auto &step : m_steps)
    2023             :                 {
    2024          29 :                     if (!step->m_outputVRTCompatible)
    2025             :                     {
    2026          12 :                         step->ReportError(
    2027             :                             CE_Failure, CPLE_NotSupported,
    2028             :                             "VRT output is not supported. Consider using the "
    2029             :                             "GDALG driver instead (files with .gdalg.json "
    2030             :                             "extension)");
    2031          12 :                         return RunStepState::ERROR;
    2032             :                     }
    2033             :                 }
    2034             :             }
    2035             :         }
    2036             :     }
    2037             : 
    2038         394 :     if (m_executionForStreamOutput &&
    2039          44 :         !CPLTestBool(
    2040             :             CPLGetConfigOption("GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM", "NO")))
    2041             :     {
    2042             :         // For security reasons, to avoid that reading a .gdalg.json file writes
    2043             :         // a file on the file system.
    2044          99 :         for (const auto &step : m_steps)
    2045             :         {
    2046          62 :             if (step->GetName() == GDALRasterWriteAlgorithm::NAME)
    2047             :             {
    2048           3 :                 if (!EQUAL(step->m_format.c_str(), "stream"))
    2049             :                 {
    2050           2 :                     ReportError(CE_Failure, CPLE_AppDefined,
    2051             :                                 "in streamed execution, --format "
    2052             :                                 "stream should be used");
    2053           5 :                     return RunStepState::ERROR;
    2054             :                 }
    2055             :             }
    2056          59 :             else if (step->GeneratesFilesFromUserInput())
    2057             :             {
    2058           3 :                 ReportError(CE_Failure, CPLE_AppDefined,
    2059             :                             "Step '%s' not allowed in stream execution, unless "
    2060             :                             "the GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM "
    2061             :                             "configuration option is set.",
    2062           3 :                             step->GetName().c_str());
    2063           3 :                 return RunStepState::ERROR;
    2064             :             }
    2065             :         }
    2066             :     }
    2067             : 
    2068         345 :     return RunStepState::GO_ON;
    2069             : }
    2070             : 
    2071             : /************************************************************************/
    2072             : /*                   RunStepDealWithMultiProcessing()                   */
    2073             : /************************************************************************/
    2074             : 
    2075             : GDALAbstractPipelineAlgorithm::RunStepState
    2076         345 : GDALAbstractPipelineAlgorithm::RunStepDealWithMultiProcessing(
    2077             :     GDALPipelineStepRunContext &ctxt)
    2078             : {
    2079             :     // Because of multiprocessing in gdal raster tile, make sure that all
    2080             :     // steps before it are either materialized or serialized in a .gdal.json file
    2081         289 :     if (m_steps.size() >= 2 && m_steps.back()->SupportsInputMultiThreading() &&
    2082           7 :         m_steps.back()
    2083           7 :                 ->GetArg(GDAL_ARG_NAME_NUM_THREADS_INT_HIDDEN)
    2084         644 :                 ->Get<int>() > 1 &&
    2085           7 :         !(m_steps.size() == 2 &&
    2086           3 :           m_steps[0]->GetName() == GDALRasterReadAlgorithm::NAME))
    2087             :     {
    2088           6 :         if (m_steps[m_steps.size() - 2]->GetName() ==
    2089             :             GDALMaterializeRasterAlgorithm::NAME)
    2090             :         {
    2091             :             // If the step immediately before the last one is materialize,
    2092             :             // then make sure the file it generates can be re-opened
    2093           3 :             auto poArg = m_steps[m_steps.size() - 2]->GetArg(
    2094             :                 GDALMaterializeRasterAlgorithm::
    2095             :                     ARG_NAME_REOPEN_AND_DO_NOT_EARLY_DELETE);
    2096           3 :             CPLAssert(poArg);
    2097           3 :             poArg->Set(true);
    2098             :         }
    2099             :         else
    2100             :         {
    2101           3 :             for (size_t i = m_steps.size() - 2; i > 0;)
    2102             :             {
    2103           1 :                 --i;
    2104           1 :                 if (m_steps[i]->GetName() ==
    2105             :                     GDALMaterializeRasterAlgorithm::NAME)
    2106             :                 {
    2107           1 :                     auto poArg = m_steps[i]->GetArg(GDAL_ARG_NAME_OUTPUT);
    2108           1 :                     CPLAssert(poArg);
    2109           1 :                     if (poArg->IsExplicitlySet())
    2110             :                     {
    2111             :                         // We could potentially support that scenario but that
    2112             :                         // would require executing the pipeline up to that
    2113             :                         // step, and serializing the rest to GDALG
    2114           1 :                         ReportError(
    2115             :                             CE_Failure, CPLE_AppDefined,
    2116             :                             "Cannot execute this pipeline in parallel mode due "
    2117             :                             "to the presence of a materialize step that has a "
    2118             :                             "'output' argument and is not immediately before "
    2119             :                             "the last step. "
    2120             :                             "Move/create a materialize step immediately before "
    2121             :                             "the last step, or add '-j 1' to the last step "
    2122             :                             "'%s'",
    2123           1 :                             m_steps.back()->GetName().c_str());
    2124           1 :                         return RunStepState::ERROR;
    2125             :                     }
    2126             :                 }
    2127             :             }
    2128             : 
    2129           2 :             bool ret = false;
    2130           2 :             auto poSrcDS = m_inputDataset.size() == 1
    2131           2 :                                ? m_inputDataset[0].GetDatasetRef()
    2132           2 :                                : nullptr;
    2133           2 :             if (poSrcDS)
    2134             :             {
    2135           1 :                 auto poSrcDriver = poSrcDS->GetDriver();
    2136           1 :                 if (!poSrcDriver || EQUAL(poSrcDriver->GetDescription(), "MEM"))
    2137             :                 {
    2138           1 :                     ReportError(
    2139             :                         CE_Failure, CPLE_AppDefined,
    2140             :                         "Cannot execute this pipeline in parallel mode due to "
    2141             :                         "input dataset of last step being a non-materialized "
    2142             :                         "dataset. "
    2143             :                         "Materialize it first, or add '-j 1' to the last step "
    2144             :                         "'%s'",
    2145           1 :                         m_steps.back()->GetName().c_str());
    2146           1 :                     return RunStepState::ERROR;
    2147             :                 }
    2148             :             }
    2149           2 :             std::string outString;
    2150           1 :             if (SaveGDALGIntoFileOrString(std::string(), outString))
    2151             :             {
    2152           1 :                 const char *const apszAllowedDrivers[] = {"GDALG", nullptr};
    2153           1 :                 auto poCurDS = GDALDataset::Open(
    2154             :                     outString.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    2155             :                     apszAllowedDrivers);
    2156           1 :                 if (poCurDS)
    2157             :                 {
    2158           1 :                     auto &lastAlg = m_steps.back();
    2159           1 :                     lastAlg->m_inputDataset.clear();
    2160           1 :                     lastAlg->m_inputDataset.resize(1);
    2161           1 :                     lastAlg->m_inputDataset[0].Set(poCurDS);
    2162           1 :                     lastAlg->m_inputDataset[0].SetDatasetOpenedByAlgorithm();
    2163           1 :                     poCurDS->Release();
    2164           1 :                     ret = lastAlg->RunStep(ctxt);
    2165           1 :                     lastAlg->m_inputDataset[0].Close();
    2166             :                 }
    2167             :             }
    2168             :             else
    2169             :             {
    2170           0 :                 ReportError(
    2171             :                     CE_Failure, CPLE_AppDefined,
    2172             :                     "Cannot execute this pipeline in parallel mode due to "
    2173             :                     "an unexpected error. "
    2174             :                     "Trying adding a materialize step before the last step "
    2175             :                     "'%s', or add '-j 1' to the last step.",
    2176           0 :                     m_steps.back()->GetName().c_str());
    2177           0 :                 return RunStepState::ERROR;
    2178             :             }
    2179           1 :             return ret ? RunStepState::PROCESSED : RunStepState::ERROR;
    2180             :         }
    2181             :     }
    2182             : 
    2183         342 :     return RunStepState::GO_ON;
    2184             : }
    2185             : 
    2186             : /************************************************************************/
    2187             : /*                RunStepDealWithStepUnknownInputType()                 */
    2188             : /************************************************************************/
    2189             : 
    2190          21 : bool GDALAbstractPipelineAlgorithm::RunStepDealWithStepUnknownInputType(
    2191             :     size_t i, int nCurDatasetType)
    2192             : {
    2193             :     // We go here if there was a step such as "external" where at
    2194             :     // ParseCommandLineArguments() time we could not determine its
    2195             :     // type of output dataset. Now we must check for steps afterwards
    2196             :     // such as "write" or "reproject" that exist both as separate raster
    2197             :     // and vector commands if the one we initially picked is appropriate.
    2198             :     // If not, then switch to the other type.
    2199             : 
    2200             :     // GetInputDatasetType() is to deal with pipelines like:
    2201             :     // gdal pipeline read input_vector !
    2202             :     //      external --command "cp <INPUT> <OUTPUT>" !
    2203             :     //      clip --input raster_dataset --like _PIPE_ !
    2204             :     //      write output_raster
    2205             : 
    2206          21 :     auto &step = m_steps[i];
    2207          21 :     const int nThisDatasetType = GetInputDatasetType(step.get());
    2208          21 :     const int nThisStepType =
    2209          21 :         nThisDatasetType ? nThisDatasetType : nCurDatasetType;
    2210          21 :     if (step->GetInputType() != 0 && step->GetInputType() != nThisStepType)
    2211             :     {
    2212             :         auto newAlg = GetStepAlg(
    2213           6 :             step->GetName() +
    2214           6 :             (nThisStepType == GDAL_OF_RASTER ? RASTER_SUFFIX : VECTOR_SUFFIX));
    2215           6 :         if (newAlg)
    2216             :         {
    2217             :             const bool maybeWriteStep =
    2218           9 :                 (i == m_steps.size() - 1 &&
    2219           3 :                  m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE);
    2220           6 :             if (!CopyStepAlgorithmFromAnother(newAlg.get(), step.get(),
    2221             :                                               maybeWriteStep))
    2222           0 :                 return false;
    2223           6 :             newAlg->m_inputDatasetCanBeOmitted = true;
    2224             : 
    2225           6 :             step = std::move(newAlg);
    2226             :         }
    2227             :     }
    2228             : 
    2229          21 :     return step->ValidateArguments();
    2230             : }
    2231             : 
    2232             : /************************************************************************/
    2233             : /*                CheckStepHasNoInputDatasetAlreadySet()                */
    2234             : /************************************************************************/
    2235             : 
    2236         432 : bool GDALAbstractPipelineAlgorithm::CheckStepHasNoInputDatasetAlreadySet(
    2237             :     size_t i, GDALDataset *poCurDS)
    2238             : {
    2239         432 :     auto &step = *(m_steps[i]);
    2240         432 :     bool prevStepOutputSetToThisStep = false;
    2241        6429 :     for (auto &arg : step.GetArgs())
    2242             :     {
    2243       11684 :         if (!arg->IsOutput() && (arg->GetType() == GAAT_DATASET ||
    2244        5687 :                                  arg->GetType() == GAAT_DATASET_LIST))
    2245             :         {
    2246         512 :             if (arg->GetType() == GAAT_DATASET)
    2247             :             {
    2248          60 :                 if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
    2249         120 :                      !arg->IsExplicitlySet()) ||
    2250          60 :                     arg->Get<GDALArgDatasetValue>().GetName() ==
    2251             :                         GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
    2252             :                 {
    2253          14 :                     auto &val = arg->Get<GDALArgDatasetValue>();
    2254          14 :                     if (val.GetDatasetRef())
    2255             :                     {
    2256             :                         // Shouldn't happen
    2257           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
    2258             :                                     "Step nr %d (%s) has already an "
    2259             :                                     "input dataset for argument %s",
    2260           0 :                                     static_cast<int>(i), step.GetName().c_str(),
    2261           0 :                                     arg->GetName().c_str());
    2262           0 :                         return false;
    2263             :                     }
    2264          14 :                     prevStepOutputSetToThisStep = true;
    2265          14 :                     val.Set(poCurDS);
    2266          14 :                     arg->NotifyValueSet();
    2267             :                 }
    2268             :             }
    2269             :             else
    2270             :             {
    2271         452 :                 CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
    2272         452 :                 auto &val = arg->Get<std::vector<GDALArgDatasetValue>>();
    2273         452 :                 if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
    2274         518 :                      !arg->IsExplicitlySet()) ||
    2275          66 :                     (val.size() == 1 &&
    2276          30 :                      val[0].GetName() ==
    2277             :                          GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE))
    2278             :                 {
    2279         419 :                     if (val.size() == 1 && val[0].GetDatasetRef())
    2280             :                     {
    2281             :                         // Shouldn't happen
    2282           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
    2283             :                                     "Step nr %d (%s) has already an "
    2284             :                                     "input dataset for argument %s",
    2285           0 :                                     static_cast<int>(i), step.GetName().c_str(),
    2286           0 :                                     arg->GetName().c_str());
    2287           0 :                         return false;
    2288             :                     }
    2289         419 :                     prevStepOutputSetToThisStep = true;
    2290         419 :                     val.clear();
    2291         419 :                     val.resize(1);
    2292         419 :                     val[0].Set(poCurDS);
    2293         419 :                     arg->NotifyValueSet();
    2294             :                 }
    2295             :             }
    2296             :         }
    2297             :     }
    2298         432 :     if (!prevStepOutputSetToThisStep)
    2299             :     {
    2300           1 :         ReportError(CE_Failure, CPLE_AppDefined,
    2301             :                     "Step nr %d (%s) does not use input dataset from "
    2302             :                     "previous step",
    2303           1 :                     static_cast<int>(i), step.GetName().c_str());
    2304           1 :         return false;
    2305             :     }
    2306             : 
    2307         431 :     return true;
    2308             : }
    2309             : 
    2310             : /************************************************************************/
    2311             : /*               GDALAbstractPipelineAlgorithm::RunStep()               */
    2312             : /************************************************************************/
    2313             : 
    2314         424 : bool GDALAbstractPipelineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
    2315             : {
    2316         424 :     if (m_stepOnWhichHelpIsRequested)
    2317             :     {
    2318           6 :         printf(
    2319             :             "%s",
    2320          12 :             m_stepOnWhichHelpIsRequested->GetUsageForCLI(false).c_str()); /*ok*/
    2321           6 :         return true;
    2322             :     }
    2323             : 
    2324         418 :     if (m_steps.empty())
    2325             :     {
    2326             :         // If invoked programmatically, not from the command line.
    2327             : 
    2328         261 :         if (m_pipeline.empty())
    2329             :         {
    2330          12 :             ReportError(CE_Failure, CPLE_AppDefined,
    2331             :                         "'pipeline' argument not set");
    2332          43 :             return false;
    2333             :         }
    2334             : 
    2335         249 :         const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
    2336         249 :         if (!ParseCommandLineArguments(aosTokens))
    2337          31 :             return false;
    2338             :     }
    2339             : 
    2340         375 :     switch (RunStepDealWithGDALGJson())
    2341             :     {
    2342          11 :         case RunStepState::PROCESSED:
    2343          11 :             return true;
    2344          19 :         case RunStepState::ERROR:
    2345          19 :             return false;
    2346         345 :         case RunStepState::GO_ON:
    2347         345 :             break;
    2348             :     }
    2349         345 :     switch (RunStepDealWithMultiProcessing(ctxt))
    2350             :     {
    2351           1 :         case RunStepState::PROCESSED:
    2352           1 :             return true;
    2353           2 :         case RunStepState::ERROR:
    2354           2 :             return false;
    2355         342 :         case RunStepState::GO_ON:
    2356         342 :             break;
    2357             :     }
    2358             : 
    2359         342 :     int countPipelinesWithProgress = 0;
    2360         802 :     for (size_t i = (m_bExpectReadStep ? 0 : 1); i < m_steps.size(); ++i)
    2361             :     {
    2362             :         const bool bCanHandleNextStep =
    2363         769 :             i < m_steps.size() - 1 &&
    2364         309 :             !m_steps[i]->CanHandleNextStep(m_steps[i + 1].get());
    2365         769 :         if (bCanHandleNextStep &&
    2366         309 :             !m_steps[i + 1]->IsNativelyStreamingCompatible())
    2367         182 :             ++countPipelinesWithProgress;
    2368         278 :         else if (!m_steps[i]->IsNativelyStreamingCompatible())
    2369         107 :             ++countPipelinesWithProgress;
    2370         460 :         if (bCanHandleNextStep)
    2371         309 :             ++i;
    2372             :     }
    2373         342 :     if (countPipelinesWithProgress == 0)
    2374         114 :         countPipelinesWithProgress = 1;
    2375             : 
    2376         342 :     bool ret = true;
    2377         342 :     GDALDataset *poCurDS = nullptr;
    2378         342 :     int iCurStepWithProgress = 0;
    2379             : 
    2380         342 :     if (!m_bExpectReadStep)
    2381             :     {
    2382          17 :         CPLAssert(m_inputDataset.size() == 1);
    2383          17 :         poCurDS = m_inputDataset[0].GetDatasetRef();
    2384          17 :         CPLAssert(poCurDS);
    2385             :     }
    2386             : 
    2387         342 :     GDALProgressFunc pfnProgress = ctxt.m_pfnProgress;
    2388         342 :     void *pProgressData = ctxt.m_pProgressData;
    2389         342 :     if (IsCalledFromCommandLine() && HasOutputString())
    2390             :     {
    2391           6 :         pfnProgress = nullptr;
    2392           6 :         pProgressData = nullptr;
    2393             :     }
    2394             : 
    2395         342 :     int nCurDatasetType = poCurDS ? GetDatasetType(poCurDS) : 0;
    2396             : 
    2397        1058 :     for (size_t i = 0; i < m_steps.size(); ++i)
    2398             :     {
    2399         757 :         auto &step = m_steps[i];
    2400             : 
    2401         415 :         if (i > 0 && m_nFirstStepWithUnknownInputType >= 0 &&
    2402          45 :             i >= static_cast<size_t>(m_nFirstStepWithUnknownInputType) &&
    2403        1172 :             nCurDatasetType != 0 && GetStepAlg(step->GetName()) == nullptr)
    2404             :         {
    2405          21 :             if (!RunStepDealWithStepUnknownInputType(i, nCurDatasetType))
    2406           1 :                 return false;
    2407             :         }
    2408             : 
    2409         757 :         if (i > 0 || poCurDS)
    2410             :         {
    2411         432 :             if (!CheckStepHasNoInputDatasetAlreadySet(i, poCurDS))
    2412           1 :                 return false;
    2413             :         }
    2414             : 
    2415         758 :         if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef() &&
    2416           2 :             !step->OutputDatasetAllowedBeforeRunningStep())
    2417             :         {
    2418             :             // Shouldn't happen
    2419           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    2420             :                         "Step nr %d (%s) has already an output dataset",
    2421           0 :                         static_cast<int>(i), step->GetName().c_str());
    2422           0 :             return false;
    2423             :         }
    2424             : 
    2425             :         const bool bCanHandleNextStep =
    2426        1200 :             i < m_steps.size() - 1 &&
    2427         444 :             step->CanHandleNextStep(m_steps[i + 1].get());
    2428             : 
    2429             :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
    2430         756 :             nullptr, GDALDestroyScaledProgress);
    2431         756 :         GDALPipelineStepRunContext stepCtxt;
    2432         766 :         if ((bCanHandleNextStep &&
    2433        1512 :              m_steps[i + 1]->IsNativelyStreamingCompatible()) ||
    2434         756 :             !step->IsNativelyStreamingCompatible())
    2435             :         {
    2436         279 :             pScaledData.reset(GDALCreateScaledProgress(
    2437             :                 iCurStepWithProgress /
    2438         279 :                     static_cast<double>(countPipelinesWithProgress),
    2439         279 :                 (iCurStepWithProgress + 1) /
    2440         279 :                     static_cast<double>(countPipelinesWithProgress),
    2441             :                 pfnProgress, pProgressData));
    2442         279 :             ++iCurStepWithProgress;
    2443         279 :             stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
    2444         279 :             stepCtxt.m_pProgressData = pScaledData.get();
    2445             :         }
    2446         756 :         if (bCanHandleNextStep)
    2447             :         {
    2448          10 :             stepCtxt.m_poNextUsableStep = m_steps[i + 1].get();
    2449             :         }
    2450         773 :         if (i + 1 == m_steps.size() && m_stdout &&
    2451         773 :             step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr)
    2452             :         {
    2453           4 :             step->m_stdout = true;
    2454             :         }
    2455         756 :         step->m_inputDatasetCanBeOmitted = false;
    2456         756 :         step->m_quiet = m_quiet;
    2457         756 :         if (!step->ValidateArguments() || !step->RunStep(stepCtxt))
    2458             :         {
    2459          40 :             ret = false;
    2460          40 :             break;
    2461             :         }
    2462         716 :         poCurDS = step->m_outputDataset.GetDatasetRef();
    2463         716 :         nCurDatasetType = 0;
    2464         716 :         if (poCurDS)
    2465             :         {
    2466         690 :             nCurDatasetType = GetDatasetType(poCurDS);
    2467             :         }
    2468          64 :         else if (!(i + 1 == m_steps.size() &&
    2469          26 :                    (!step->m_output.empty() ||
    2470          38 :                     step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr ||
    2471           7 :                     step->GetOutputType() == 0)))
    2472             :         {
    2473           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    2474             :                         "Step nr %d (%s) failed to produce an output dataset",
    2475           0 :                         static_cast<int>(i), step->GetName().c_str());
    2476           0 :             return false;
    2477             :         }
    2478             : 
    2479         716 :         m_output += step->GetOutputString();
    2480             : 
    2481         716 :         if (bCanHandleNextStep)
    2482             :         {
    2483          10 :             ++i;
    2484             :         }
    2485             :     }
    2486             : 
    2487         341 :     if (pfnProgress && m_output.empty())
    2488          16 :         pfnProgress(1.0, "", pProgressData);
    2489             : 
    2490         341 :     if (!m_output.empty())
    2491             :     {
    2492          17 :         auto outputStringArg = GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
    2493          17 :         if (outputStringArg && outputStringArg->GetType() == GAAT_STRING)
    2494          17 :             outputStringArg->Set(m_output);
    2495             :     }
    2496             : 
    2497         341 :     if (ret && poCurDS && !m_outputDataset.GetDatasetRef())
    2498             :     {
    2499         274 :         m_outputDataset.Set(poCurDS);
    2500             :     }
    2501             : 
    2502         341 :     return ret;
    2503             : }
    2504             : 
    2505             : /************************************************************************/
    2506             : /*           GDALAbstractPipelineAlgorithm::HasOutputString()           */
    2507             : /************************************************************************/
    2508             : 
    2509          35 : bool GDALAbstractPipelineAlgorithm::HasOutputString() const
    2510             : {
    2511         103 :     for (const auto &step : m_steps)
    2512             :     {
    2513          74 :         if (step->HasOutputString())
    2514           6 :             return true;
    2515             :     }
    2516          29 :     return false;
    2517             : }
    2518             : 
    2519             : /************************************************************************/
    2520             : /*              GDALAbstractPipelineAlgorithm::Finalize()               */
    2521             : /************************************************************************/
    2522             : 
    2523         236 : bool GDALAbstractPipelineAlgorithm::Finalize()
    2524             : {
    2525         236 :     bool ret = GDALPipelineStepAlgorithm::Finalize();
    2526             :     // Finalize steps in reverse order, typically to make sure later steps
    2527             :     // have dropped their reference on datasets passed by previous ones.
    2528             :     // This helps for example for the "external" step that needs to delete
    2529             :     // temporary files.
    2530         751 :     for (auto iter = m_steps.rbegin(); iter != m_steps.rend(); ++iter)
    2531             :     {
    2532         515 :         ret = (*iter)->Finalize() && ret;
    2533             :     }
    2534         236 :     return ret;
    2535             : }
    2536             : 
    2537             : /************************************************************************/
    2538             : /*           GDALAbstractPipelineAlgorithm::GetUsageAsJSON()            */
    2539             : /************************************************************************/
    2540             : 
    2541           9 : std::string GDALAbstractPipelineAlgorithm::GetUsageAsJSON() const
    2542             : {
    2543          18 :     CPLJSONDocument oDoc;
    2544           9 :     CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
    2545             : 
    2546          18 :     CPLJSONArray jPipelineSteps;
    2547         378 :     for (const std::string &name : GetStepRegistry().GetNames())
    2548             :     {
    2549         738 :         auto alg = GetStepAlg(name);
    2550         369 :         if (!alg->IsHidden())
    2551             :         {
    2552         369 :             CPLJSONDocument oStepDoc;
    2553         369 :             CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
    2554         369 :             jPipelineSteps.Add(oStepDoc.GetRoot());
    2555             :         }
    2556             :     }
    2557           9 :     oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
    2558             : 
    2559          18 :     return oDoc.SaveAsString();
    2560             : }
    2561             : 
    2562             : //! @endcond

Generated by: LCOV version 1.14