LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1200 1227 97.8 %
Date: 2026-06-23 16:35:19 Functions: 24 24 100.0 %

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

Generated by: LCOV version 1.14