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

Generated by: LCOV version 1.14