LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 943 970 97.2 %
Date: 2025-09-10 17:48:50 Functions: 16 16 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_error_internal.h"
      15             : #include "cpl_json.h"
      16             : 
      17             : #include "gdalalg_abstract_pipeline.h"
      18             : #include "gdalalg_raster_read.h"
      19             : #include "gdalalg_raster_write.h"
      20             : #include "gdalalg_tee.h"
      21             : 
      22             : #include <algorithm>
      23             : #include <cassert>
      24             : 
      25             : //! @cond Doxygen_Suppress
      26             : 
      27             : /* clang-format off */
      28             : constexpr const char *const apszReadParametersPrefixOmitted[] = {
      29             :     GDAL_ARG_NAME_INPUT,
      30             :     GDAL_ARG_NAME_INPUT_FORMAT,
      31             :     GDAL_ARG_NAME_OPEN_OPTION,
      32             :     GDAL_ARG_NAME_INPUT_LAYER};
      33             : 
      34             : constexpr const char *const apszWriteParametersPrefixOmitted[] = {
      35             :     GDAL_ARG_NAME_OUTPUT,
      36             :     GDAL_ARG_NAME_OUTPUT_FORMAT,
      37             :     GDAL_ARG_NAME_CREATION_OPTION,
      38             :     GDAL_ARG_NAME_OUTPUT_LAYER,
      39             :     GDAL_ARG_NAME_LAYER_CREATION_OPTION,
      40             :     GDAL_ARG_NAME_UPDATE,
      41             :     GDAL_ARG_NAME_OVERWRITE,
      42             :     GDAL_ARG_NAME_APPEND,
      43             :     GDAL_ARG_NAME_OVERWRITE_LAYER};
      44             : 
      45             : /* clang-format on */
      46             : 
      47             : /************************************************************************/
      48             : /*                     IsReadSpecificArgument()                         */
      49             : /************************************************************************/
      50             : 
      51             : /* static */
      52          35 : bool GDALAbstractPipelineAlgorithm::IsReadSpecificArgument(
      53             :     const char *pszArgName)
      54             : {
      55          35 :     return std::find_if(std::begin(apszReadParametersPrefixOmitted),
      56             :                         std::end(apszReadParametersPrefixOmitted),
      57         114 :                         [pszArgName](const char *pszStr) {
      58         114 :                             return strcmp(pszStr, pszArgName) == 0;
      59          35 :                         }) != std::end(apszReadParametersPrefixOmitted);
      60             : }
      61             : 
      62             : /************************************************************************/
      63             : /*                     IsWriteSpecificArgument()                        */
      64             : /************************************************************************/
      65             : 
      66             : /* static */
      67          50 : bool GDALAbstractPipelineAlgorithm::IsWriteSpecificArgument(
      68             :     const char *pszArgName)
      69             : {
      70          50 :     return std::find_if(std::begin(apszWriteParametersPrefixOmitted),
      71             :                         std::end(apszWriteParametersPrefixOmitted),
      72         222 :                         [pszArgName](const char *pszStr) {
      73         222 :                             return strcmp(pszStr, pszArgName) == 0;
      74          50 :                         }) != std::end(apszWriteParametersPrefixOmitted);
      75             : }
      76             : 
      77             : /************************************************************************/
      78             : /*          GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep()      */
      79             : /************************************************************************/
      80             : 
      81         265 : bool GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep(
      82             :     const std::vector<GDALPipelineStepAlgorithm *> &steps) const
      83             : {
      84         265 :     if (m_bExpectReadStep && !steps.front()->CanBeFirstStep())
      85             :     {
      86           6 :         std::set<CPLString> setFirstStepNames;
      87         128 :         for (const auto &stepName : GetStepRegistry().GetNames())
      88             :         {
      89         250 :             auto alg = GetStepAlg(stepName);
      90         125 :             if (alg && alg->CanBeFirstStep() && stepName != "read")
      91             :             {
      92          20 :                 setFirstStepNames.insert(CPLString(stepName)
      93          20 :                                              .replaceAll(RASTER_SUFFIX, "")
      94          10 :                                              .replaceAll(VECTOR_SUFFIX, ""));
      95             :             }
      96             :         }
      97          15 :         std::vector<std::string> firstStepNames{"read"};
      98          12 :         for (const std::string &s : setFirstStepNames)
      99           9 :             firstStepNames.push_back(s);
     100             : 
     101           3 :         std::string msg = "First step should be ";
     102          15 :         for (size_t i = 0; i < firstStepNames.size(); ++i)
     103             :         {
     104          12 :             if (i == firstStepNames.size() - 1)
     105           3 :                 msg += " or ";
     106           9 :             else if (i > 0)
     107           6 :                 msg += ", ";
     108          12 :             msg += '\'';
     109          12 :             msg += firstStepNames[i];
     110          12 :             msg += '\'';
     111             :         }
     112             : 
     113           3 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
     114           3 :         return false;
     115             :     }
     116             : 
     117         262 :     if (!m_bExpectReadStep)
     118             :     {
     119          11 :         if (steps.front()->CanBeFirstStep())
     120             :         {
     121           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     122             :                         "No read-like step like '%s' is allowed",
     123           1 :                         steps.front()->GetName().c_str());
     124           1 :             return false;
     125             :         }
     126             :     }
     127         261 :     if (!m_bExpectWriteStep)
     128             :     {
     129           6 :         if (steps.back()->CanBeLastStep() && !steps.back()->CanBeMiddleStep())
     130             :         {
     131           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     132             :                         "No write-like step like '%s' is allowed",
     133           0 :                         steps.back()->GetName().c_str());
     134           0 :             return false;
     135             :         }
     136             :     }
     137             : 
     138         392 :     for (size_t i = 1; i < steps.size() - 1; ++i)
     139             :     {
     140         141 :         if (m_bExpectReadStep && steps[i]->CanBeFirstStep() &&
     141           3 :             !steps[i]->CanBeMiddleStep())
     142             :         {
     143           3 :             ReportError(CE_Failure, CPLE_AppDefined,
     144             :                         "Only first step can be '%s'",
     145           3 :                         steps[i]->GetName().c_str());
     146           3 :             return false;
     147             :         }
     148         144 :         else if (m_bExpectWriteStep && steps[i]->CanBeLastStep() &&
     149           9 :                  !steps[i]->CanBeMiddleStep())
     150             :         {
     151           3 :             ReportError(CE_Failure, CPLE_AppDefined,
     152             :                         "Only last step can be '%s'",
     153           3 :                         steps[i]->GetName().c_str());
     154           3 :             return false;
     155             :         }
     156         132 :         else if (!steps[i]->CanBeMiddleStep())
     157             :         {
     158           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     159             :                         "'%s' is not allowed as an intermediate step",
     160           1 :                         steps[i]->GetName().c_str());
     161           1 :             return false;
     162             :         }
     163             :     }
     164             : 
     165         254 :     if (m_bExpectWriteStep && !steps.back()->CanBeLastStep())
     166             :     {
     167          26 :         std::set<CPLString> setLastStepNames;
     168         608 :         for (const auto &stepName : GetStepRegistry().GetNames())
     169             :         {
     170        1190 :             auto alg = GetStepAlg(stepName);
     171         595 :             if (alg && alg->CanBeLastStep() && stepName != "write")
     172             :             {
     173         156 :                 setLastStepNames.insert(CPLString(stepName)
     174         156 :                                             .replaceAll(RASTER_SUFFIX, "")
     175          78 :                                             .replaceAll(VECTOR_SUFFIX, ""));
     176             :             }
     177             :         }
     178          65 :         std::vector<std::string> lastStepNames{"write"};
     179          73 :         for (const std::string &s : setLastStepNames)
     180          60 :             lastStepNames.push_back(s);
     181             : 
     182          13 :         std::string msg = "Last step should be ";
     183          86 :         for (size_t i = 0; i < lastStepNames.size(); ++i)
     184             :         {
     185          73 :             if (i == lastStepNames.size() - 1)
     186          13 :                 msg += " or ";
     187          60 :             else if (i > 0)
     188          47 :                 msg += ", ";
     189          73 :             msg += '\'';
     190          73 :             msg += lastStepNames[i];
     191          73 :             msg += '\'';
     192             :         }
     193             : 
     194          13 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
     195          13 :         return false;
     196             :     }
     197             : 
     198         241 :     return true;
     199             : }
     200             : 
     201             : /************************************************************************/
     202             : /*              GDALAbstractPipelineAlgorithm::GetStepAlg()             */
     203             : /************************************************************************/
     204             : 
     205             : std::unique_ptr<GDALPipelineStepAlgorithm>
     206        3003 : GDALAbstractPipelineAlgorithm::GetStepAlg(const std::string &name) const
     207             : {
     208        6006 :     auto alg = GetStepRegistry().Instantiate(name);
     209             :     return std::unique_ptr<GDALPipelineStepAlgorithm>(
     210        6006 :         cpl::down_cast<GDALPipelineStepAlgorithm *>(alg.release()));
     211             : }
     212             : 
     213             : /************************************************************************/
     214             : /*       GDALAbstractPipelineAlgorithm::ParseCommandLineArguments()     */
     215             : /************************************************************************/
     216             : 
     217         301 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
     218             :     const std::vector<std::string> &argsIn)
     219             : {
     220         301 :     return ParseCommandLineArguments(argsIn, /*forAutoComplete=*/false);
     221             : }
     222             : 
     223         355 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
     224             :     const std::vector<std::string> &argsIn, bool forAutoComplete)
     225             : {
     226         710 :     std::vector<std::string> args = argsIn;
     227             : 
     228         411 :     if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
     229          56 :                              args[0] == "help" || args[0] == "--json-usage"))
     230             :     {
     231           5 :         return GDALAlgorithm::ParseCommandLineArguments(args);
     232             :     }
     233         350 :     else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
     234             :     {
     235           9 :         m_helpDocCategory = args[0].substr(strlen("--help-doc="));
     236          18 :         return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
     237             :     }
     238             : 
     239         341 :     bool foundStepMarker = false;
     240             : 
     241        2243 :     for (size_t i = 0; i < args.size(); ++i)
     242             :     {
     243        1912 :         const auto &arg = args[i];
     244        1912 :         if (arg == "--pipeline")
     245             :         {
     246          10 :             if (i + 1 < args.size() &&
     247          10 :                 CPLString(args[i + 1]).ifind(".json") != std::string::npos)
     248           2 :                 break;
     249           3 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     250             :         }
     251             : 
     252        1907 :         else if (cpl::starts_with(arg, "--pipeline="))
     253             :         {
     254           2 :             if (CPLString(arg).ifind(".json") != std::string::npos)
     255           1 :                 break;
     256           1 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     257             :         }
     258             : 
     259             :         // gdal pipeline [--quiet] "read poly.gpkg ..."
     260        1905 :         if (arg.find("read ") == 0)
     261           3 :             return GDALAlgorithm::ParseCommandLineArguments(args);
     262             : 
     263        1902 :         if (arg == "!")
     264         360 :             foundStepMarker = true;
     265             :     }
     266             : 
     267         334 :     bool runExistingPipeline = false;
     268         334 :     if (!foundStepMarker && !m_executionForStreamOutput)
     269             :     {
     270          75 :         std::string osCommandLine;
     271         189 :         for (const auto &arg : args)
     272             :         {
     273         140 :             if (((!arg.empty() && arg[0] != '-') ||
     274         281 :                  cpl::starts_with(arg, "--pipeline=")) &&
     275         265 :                 CPLString(arg).ifind(".json") != std::string::npos)
     276             :             {
     277             :                 bool ret;
     278          26 :                 if (m_pipeline == arg)
     279           2 :                     ret = true;
     280             :                 else
     281             :                 {
     282             :                     const std::string filename =
     283          24 :                         cpl::starts_with(arg, "--pipeline=")
     284             :                             ? arg.substr(strlen("--pipeline="))
     285          48 :                             : arg;
     286          24 :                     if (forAutoComplete)
     287             :                     {
     288           6 :                         SetParseForAutoCompletion();
     289             :                     }
     290          24 :                     ret = GDALAlgorithm::ParseCommandLineArguments(args) ||
     291             :                           forAutoComplete;
     292          24 :                     if (ret)
     293             :                     {
     294          21 :                         ret = m_pipeline == filename;
     295             :                     }
     296             :                 }
     297          26 :                 if (ret)
     298             :                 {
     299          23 :                     CPLJSONDocument oDoc;
     300          23 :                     ret = oDoc.Load(m_pipeline);
     301          23 :                     if (ret)
     302             :                     {
     303             :                         osCommandLine =
     304          22 :                             oDoc.GetRoot().GetString("command_line");
     305          22 :                         if (osCommandLine.empty())
     306             :                         {
     307           1 :                             ReportError(CE_Failure, CPLE_AppDefined,
     308             :                                         "command_line missing in %s",
     309             :                                         m_pipeline.c_str());
     310           1 :                             return false;
     311             :                         }
     312             : 
     313          63 :                         for (const char *prefix :
     314             :                              {"gdal pipeline ", "gdal raster pipeline ",
     315          84 :                               "gdal vector pipeline "})
     316             :                         {
     317          63 :                             if (cpl::starts_with(osCommandLine, prefix))
     318             :                                 osCommandLine =
     319          21 :                                     osCommandLine.substr(strlen(prefix));
     320             :                         }
     321             : 
     322          21 :                         if (oDoc.GetRoot().GetBool(
     323             :                                 "relative_paths_relative_to_this_file", true))
     324             :                         {
     325           0 :                             SetReferencePathForRelativePaths(
     326           0 :                                 CPLGetPathSafe(m_pipeline.c_str()).c_str());
     327             :                         }
     328             : 
     329          21 :                         runExistingPipeline = true;
     330             :                     }
     331             :                 }
     332          25 :                 if (ret)
     333          21 :                     break;
     334             :                 else
     335           4 :                     return false;
     336             :             }
     337             :         }
     338          70 :         if (runExistingPipeline)
     339             :         {
     340             :             const CPLStringList aosArgs(
     341          21 :                 CSLTokenizeString(osCommandLine.c_str()));
     342             : 
     343          21 :             args = aosArgs;
     344             :         }
     345             :     }
     346             : 
     347         329 :     if (!m_steps.empty())
     348             :     {
     349           3 :         ReportError(CE_Failure, CPLE_AppDefined,
     350             :                     "ParseCommandLineArguments() can only be called once per "
     351             :                     "instance.");
     352           3 :         return false;
     353             :     }
     354             : 
     355             :     const bool bIsGenericPipeline =
     356         326 :         (GetInputType() == (GDAL_OF_RASTER | GDAL_OF_VECTOR));
     357             : 
     358             :     struct Step
     359             :     {
     360             :         std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
     361             :         std::vector<std::string> args{};
     362             :         bool alreadyChangedType = false;
     363             :         bool isSubAlgorithm = false;
     364             :     };
     365             : 
     366             :     const auto SetCurStepAlg =
     367        2032 :         [this, bIsGenericPipeline](Step &curStep, const std::string &algName)
     368             :     {
     369         673 :         if (bIsGenericPipeline)
     370             :         {
     371         245 :             if (algName == "read")
     372             :             {
     373         105 :                 curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
     374             :             }
     375             :             else
     376             :             {
     377         140 :                 curStep.alg = GetStepAlg(algName);
     378         140 :                 if (!curStep.alg)
     379         118 :                     curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
     380             :             }
     381             :         }
     382             :         else
     383             :         {
     384         428 :             curStep.alg = GetStepAlg(algName);
     385             :         }
     386         673 :         if (!curStep.alg)
     387             :         {
     388          14 :             ReportError(CE_Failure, CPLE_AppDefined, "unknown step name: %s",
     389             :                         algName.c_str());
     390          14 :             return false;
     391             :         }
     392        1318 :         curStep.alg->SetCallPath({algName});
     393         659 :         curStep.alg->SetReferencePathForRelativePaths(
     394             :             GetReferencePathForRelativePaths());
     395         659 :         return true;
     396         326 :     };
     397             : 
     398         652 :     std::vector<Step> steps;
     399         326 :     steps.resize(1);
     400             : 
     401         326 :     int nNestLevel = 0;
     402         652 :     std::vector<std::string> nestedPipelineArgs;
     403             : 
     404        2225 :     for (const auto &argIn : args)
     405             :     {
     406        1929 :         std::string arg(argIn);
     407             : 
     408        1929 :         auto &curStep = steps.back();
     409             : 
     410        1929 :         if (nNestLevel > 0)
     411             :         {
     412         105 :             if (arg == CLOSE_NESTED_PIPELINE)
     413             :             {
     414          25 :                 if ((--nNestLevel) == 0)
     415             :                 {
     416          50 :                     arg = BuildNestedPipeline(
     417          25 :                         curStep.alg.get(), nestedPipelineArgs, forAutoComplete);
     418          25 :                     if (arg.empty())
     419             :                     {
     420           6 :                         return false;
     421             :                     }
     422             :                 }
     423             :                 else
     424             :                 {
     425           0 :                     nestedPipelineArgs.push_back(arg);
     426           0 :                     continue;
     427             :                 }
     428             :             }
     429             :             else
     430             :             {
     431          80 :                 if (arg == OPEN_NESTED_PIPELINE)
     432             :                 {
     433           2 :                     if (++nNestLevel == MAX_NESTING_LEVEL)
     434             :                     {
     435           1 :                         ReportError(CE_Failure, CPLE_AppDefined,
     436             :                                     "Too many nested pipelines");
     437           1 :                         return false;
     438             :                     }
     439             :                 }
     440          79 :                 nestedPipelineArgs.push_back(arg);
     441          79 :                 continue;
     442             :             }
     443             :         }
     444             : 
     445        1843 :         if (arg == "--progress")
     446             :         {
     447           5 :             m_progressBarRequested = true;
     448           5 :             continue;
     449             :         }
     450        1838 :         if (arg == "--quiet")
     451             :         {
     452           0 :             m_quiet = true;
     453           0 :             m_progressBarRequested = false;
     454           0 :             continue;
     455             :         }
     456             : 
     457        1838 :         if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
     458             :         {
     459           6 :             if (!steps.back().alg)
     460           2 :                 steps.pop_back();
     461           6 :             if (steps.empty())
     462             :             {
     463           2 :                 return GDALAlgorithm::ParseCommandLineArguments(args);
     464             :             }
     465             :             else
     466             :             {
     467           4 :                 m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
     468           4 :                 return true;
     469             :             }
     470             :         }
     471             : 
     472        1832 :         if (arg == "!" || arg == "|")
     473             :         {
     474         377 :             if (curStep.alg)
     475             :             {
     476         362 :                 steps.resize(steps.size() + 1);
     477             :             }
     478             :         }
     479        1455 :         else if (arg == OPEN_NESTED_PIPELINE)
     480             :         {
     481          28 :             if (!curStep.alg)
     482             :             {
     483           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     484             :                             "Open bracket must be placed where an input "
     485             :                             "dataset is expected");
     486           1 :                 return false;
     487             :             }
     488          27 :             ++nNestLevel;
     489             :         }
     490        1427 :         else if (arg == CLOSE_NESTED_PIPELINE)
     491             :         {
     492           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     493             :                         "Closing bracket found without matching open bracket");
     494           1 :             return false;
     495             :         }
     496             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     497        1426 :         else if (arg == "+step")
     498             :         {
     499           8 :             if (curStep.alg)
     500             :             {
     501           4 :                 steps.resize(steps.size() + 1);
     502             :             }
     503             :         }
     504        1418 :         else if (arg.find("+gdal=") == 0)
     505             :         {
     506           6 :             const std::string algName = arg.substr(strlen("+gdal="));
     507           6 :             if (!SetCurStepAlg(curStep, algName))
     508           2 :                 return false;
     509             :         }
     510             : #endif
     511        1412 :         else if (!curStep.alg)
     512             :         {
     513         667 :             std::string algName = arg;
     514             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     515         667 :             if (!algName.empty() && algName[0] == '+')
     516           2 :                 algName = algName.substr(1);
     517             : #endif
     518         667 :             if (!SetCurStepAlg(curStep, algName))
     519          12 :                 return false;
     520             :         }
     521             :         else
     522             :         {
     523         745 :             if (curStep.alg->HasSubAlgorithms())
     524             :             {
     525             :                 auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
     526             :                     cpl::down_cast<GDALPipelineStepAlgorithm *>(
     527           5 :                         curStep.alg->InstantiateSubAlgorithm(arg).release()));
     528           5 :                 if (!subAlg)
     529             :                 {
     530           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     531             :                                 "'%s' is a unknown sub-algorithm of '%s'",
     532           1 :                                 arg.c_str(), curStep.alg->GetName().c_str());
     533           1 :                     return false;
     534             :                 }
     535           4 :                 curStep.isSubAlgorithm = true;
     536           4 :                 curStep.alg = std::move(subAlg);
     537           4 :                 continue;
     538             :             }
     539             : 
     540             : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
     541         749 :             if (!arg.empty() && arg[0] == '+' &&
     542           9 :                 arg.find(' ') == std::string::npos)
     543             :             {
     544           6 :                 curStep.args.push_back("--" + arg.substr(1));
     545           6 :                 continue;
     546             :             }
     547             : #endif
     548         734 :             curStep.args.push_back(arg);
     549             :         }
     550             :     }
     551             : 
     552         296 :     if (nNestLevel > 0)
     553             :     {
     554           1 :         if (forAutoComplete)
     555             :         {
     556           0 :             BuildNestedPipeline(steps.back().alg.get(), nestedPipelineArgs,
     557             :                                 forAutoComplete);
     558           0 :             return true;
     559             :         }
     560             :         else
     561             :         {
     562           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     563             :                         "Open bracket has no matching closing bracket");
     564           1 :             return false;
     565             :         }
     566             :     }
     567             : 
     568             :     // As we initially added a step without alg to bootstrap things, make
     569             :     // sure to remove it if it hasn't been filled, or the user has terminated
     570             :     // the pipeline with a '!' separator.
     571         295 :     if (!steps.back().alg)
     572          16 :         steps.pop_back();
     573             : 
     574             :     // Automatically add a final write step if none in m_executionForStreamOutput
     575             :     // mode
     576         334 :     if (m_executionForStreamOutput && m_bExpectWriteStep && !steps.empty() &&
     577          39 :         steps.back().alg->GetName() !=
     578         334 :             std::string(GDALRasterWriteAlgorithm::NAME)
     579          39 :                 .append(bIsGenericPipeline ? RASTER_SUFFIX : ""))
     580             :     {
     581          36 :         steps.resize(steps.size() + 1);
     582          36 :         steps.back().alg =
     583          72 :             GetStepAlg(std::string(GDALRasterWriteAlgorithm::NAME)
     584          72 :                            .append(bIsGenericPipeline ? RASTER_SUFFIX : ""));
     585          36 :         steps.back().args.push_back(
     586          36 :             std::string("--").append(GDAL_ARG_NAME_OUTPUT_FORMAT));
     587          36 :         steps.back().args.push_back("stream");
     588          36 :         steps.back().args.push_back("streamed_dataset");
     589             :     }
     590             : 
     591         259 :     else if (runExistingPipeline)
     592             :     {
     593             :         // Add a final "write" step if there is no explicit allowed last step
     594          21 :         if (!steps.empty() && !steps.back().alg->CanBeLastStep())
     595             :         {
     596          17 :             steps.resize(steps.size() + 1);
     597          34 :             steps.back().alg = GetStepAlg(
     598          34 :                 std::string(GDALRasterWriteAlgorithm::NAME)
     599          34 :                     .append(bIsGenericPipeline ? RASTER_SUFFIX : ""));
     600             :         }
     601             : 
     602             :         // Remove "--output-format=stream" and "streamed_dataset" if found
     603          21 :         if (steps.back().alg->GetName() == GDALRasterWriteAlgorithm::NAME)
     604             :         {
     605          29 :             for (auto oIter = steps.back().args.begin();
     606          29 :                  oIter != steps.back().args.end();)
     607             :             {
     608          24 :                 if (*oIter == std::string("--")
     609          16 :                                   .append(GDAL_ARG_NAME_OUTPUT_FORMAT)
     610          12 :                                   .append("=stream") ||
     611          16 :                     *oIter == std::string("--")
     612           4 :                                   .append(GDAL_ARG_NAME_OUTPUT)
     613          32 :                                   .append("=streamed_dataset") ||
     614           4 :                     *oIter == "streamed_dataset")
     615             :                 {
     616           8 :                     oIter = steps.back().args.erase(oIter);
     617             :                 }
     618             :                 else
     619             :                 {
     620           0 :                     ++oIter;
     621             :                 }
     622             :             }
     623             :         }
     624             :     }
     625             : 
     626         295 :     bool helpRequested = false;
     627         295 :     if (IsCalledFromCommandLine())
     628             :     {
     629         100 :         for (auto &step : steps)
     630          72 :             step.alg->SetCalledFromCommandLine();
     631             : 
     632         200 :         for (const std::string &v : args)
     633             :         {
     634         172 :             if (cpl::ends_with(v, "=?"))
     635           3 :                 helpRequested = true;
     636             :         }
     637             :     }
     638             : 
     639         295 :     if (m_bExpectWriteStep)
     640             :     {
     641         287 :         if (!m_bExpectReadStep)
     642             :         {
     643          12 :             if (steps.empty())
     644             :             {
     645           1 :                 ReportError(
     646             :                     CE_Failure, CPLE_AppDefined,
     647             :                     "At least one step must be provided in an inner pipeline.");
     648           1 :                 return false;
     649             :             }
     650             :         }
     651         275 :         else if (steps.size() < 2)
     652             :         {
     653          25 :             if (!steps.empty() && helpRequested)
     654             :             {
     655           1 :                 steps.back().alg->ParseCommandLineArguments(steps.back().args);
     656           1 :                 return false;
     657             :             }
     658             : 
     659          24 :             ReportError(CE_Failure, CPLE_AppDefined,
     660             :                         "At least 2 steps must be provided");
     661          24 :             return false;
     662             :         }
     663             : 
     664         261 :         if (!steps.back().alg->CanBeLastStep())
     665             :         {
     666          17 :             if (helpRequested)
     667             :             {
     668           2 :                 steps.back().alg->ParseCommandLineArguments(steps.back().args);
     669           2 :                 return false;
     670             :             }
     671             :         }
     672             :     }
     673             :     else
     674             :     {
     675           8 :         if (steps.empty())
     676             :         {
     677           1 :             ReportError(
     678             :                 CE_Failure, CPLE_AppDefined,
     679             :                 "At least one step must be provided in an inner pipeline.");
     680           1 :             return false;
     681             :         }
     682             : 
     683           8 :         if (steps.back().alg->CanBeLastStep() &&
     684           1 :             !steps.back().alg->CanBeMiddleStep())
     685             :         {
     686           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     687             :                         "Last step in an inner pipeline must not be a "
     688             :                         "write-like step.");
     689           1 :             return false;
     690             :         }
     691             :     }
     692             : 
     693         530 :     std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
     694         925 :     for (const auto &step : steps)
     695         660 :         stepAlgs.push_back(step.alg.get());
     696         265 :     if (!CheckFirstAndLastStep(stepAlgs))
     697          24 :         return false;  // CheckFirstAndLastStep emits an error
     698             : 
     699         847 :     for (auto &step : steps)
     700             :     {
     701         606 :         step.alg->SetReferencePathForRelativePaths(
     702             :             GetReferencePathForRelativePaths());
     703             :     }
     704             : 
     705             :     // Propagate input parameters set at the pipeline level to the
     706             :     // "read" step
     707         241 :     if (m_bExpectReadStep)
     708             :     {
     709         231 :         auto &step = steps.front();
     710        2117 :         for (auto &arg : step.alg->GetArgs())
     711             :         {
     712        1886 :             if (!arg->IsHidden())
     713             :             {
     714             :                 auto pipelineArg =
     715             :                     const_cast<const GDALAbstractPipelineAlgorithm *>(this)
     716        1653 :                         ->GetArg(arg->GetName());
     717        1684 :                 if (pipelineArg && pipelineArg->IsExplicitlySet() &&
     718          31 :                     pipelineArg->GetType() == arg->GetType())
     719             :                 {
     720          30 :                     arg->SetSkipIfAlreadySet(true);
     721          30 :                     arg->SetFrom(*pipelineArg);
     722             :                 }
     723             :             }
     724             :         }
     725             :     }
     726             : 
     727             :     // Same with "write" step
     728        3327 :     const auto SetWriteArgFromPipeline = [this, &steps]()
     729             :     {
     730         292 :         auto &step = steps.back();
     731        3843 :         for (auto &arg : step.alg->GetArgs())
     732             :         {
     733        3551 :             if (!arg->IsHidden())
     734             :             {
     735             :                 auto pipelineArg =
     736             :                     const_cast<const GDALAbstractPipelineAlgorithm *>(this)
     737        3035 :                         ->GetArg(arg->GetName());
     738        3111 :                 if (pipelineArg && pipelineArg->IsExplicitlySet() &&
     739          76 :                     pipelineArg->GetType() == arg->GetType())
     740             :                 {
     741          75 :                     arg->SetSkipIfAlreadySet(true);
     742          75 :                     arg->SetFrom(*pipelineArg);
     743             :                 }
     744             :             }
     745             :         }
     746         292 :     };
     747             : 
     748         241 :     if (m_bExpectWriteStep)
     749             :     {
     750         236 :         SetWriteArgFromPipeline();
     751             :     }
     752             : 
     753         241 :     if (runExistingPipeline)
     754             :     {
     755          21 :         std::set<std::pair<Step *, std::string>> alreadyCleanedArgs;
     756             : 
     757         283 :         for (const auto &arg : GetArgs())
     758             :         {
     759         772 :             if (arg->IsUserProvided() ||
     760         483 :                 ((arg->GetName() == GDAL_ARG_NAME_INPUT ||
     761         462 :                   arg->GetName() == GDAL_ARG_NAME_INPUT_LAYER ||
     762         441 :                   arg->GetName() == GDAL_ARG_NAME_OUTPUT ||
     763         273 :                   arg->GetName() == GDAL_ARG_NAME_OUTPUT_FORMAT) &&
     764          63 :                  arg->IsExplicitlySet()))
     765             :             {
     766             :                 CPLStringList tokens(
     767          34 :                     CSLTokenizeString2(arg->GetName().c_str(), ".", 0));
     768          34 :                 std::string stepName;
     769          34 :                 std::string stepArgName;
     770          34 :                 if (tokens.size() == 1 && IsReadSpecificArgument(tokens[0]))
     771             :                 {
     772           3 :                     stepName = steps.front().alg->GetName();
     773           3 :                     stepArgName = tokens[0];
     774             :                 }
     775          52 :                 else if (tokens.size() == 1 &&
     776          21 :                          IsWriteSpecificArgument(tokens[0]))
     777             :                 {
     778          17 :                     stepName = steps.back().alg->GetName();
     779          17 :                     stepArgName = tokens[0];
     780             :                 }
     781          14 :                 else if (tokens.size() == 2)
     782             :                 {
     783           9 :                     stepName = tokens[0];
     784           9 :                     stepArgName = tokens[1];
     785             :                 }
     786             :                 else
     787             :                 {
     788           5 :                     if (tokens.size() == 1)
     789             :                     {
     790           4 :                         const Step *matchingStep = nullptr;
     791          15 :                         for (auto &step : steps)
     792             :                         {
     793          12 :                             if (step.alg->GetArg(tokens[0]))
     794             :                             {
     795           4 :                                 if (!matchingStep)
     796           3 :                                     matchingStep = &step;
     797             :                                 else
     798             :                                 {
     799           1 :                                     ReportError(
     800             :                                         CE_Failure, CPLE_AppDefined,
     801             :                                         "Ambiguous argument name '%s', because "
     802             :                                         "it is valid for several steps in the "
     803             :                                         "pipeline. It should be specified with "
     804             :                                         "the form "
     805             :                                         "<algorithm-name>.<argument-name>.",
     806             :                                         tokens[0]);
     807           1 :                                     return false;
     808             :                                 }
     809             :                             }
     810             :                         }
     811           3 :                         if (!matchingStep)
     812             :                         {
     813           1 :                             ReportError(CE_Failure, CPLE_AppDefined,
     814             :                                         "No step in the pipeline has an "
     815             :                                         "argument named '%s'",
     816             :                                         tokens[0]);
     817           1 :                             return false;
     818             :                         }
     819           2 :                         stepName = matchingStep->alg->GetName();
     820           2 :                         stepArgName = tokens[0];
     821             :                     }
     822             :                     else
     823             :                     {
     824           1 :                         ReportError(
     825             :                             CE_Failure, CPLE_AppDefined,
     826             :                             "Invalid argument name '%s'. It should of the "
     827             :                             "form <algorithm-name>.<argument-name>.",
     828           1 :                             arg->GetName().c_str());
     829           1 :                         return false;
     830             :                     }
     831             :                 }
     832          31 :                 const auto nPosBracket = stepName.find('[');
     833          31 :                 int iRequestedStepIdx = -1;
     834          31 :                 if (nPosBracket != std::string::npos && stepName.back() == ']')
     835             :                 {
     836             :                     iRequestedStepIdx =
     837           3 :                         atoi(stepName.c_str() + nPosBracket + 1);
     838           3 :                     stepName.resize(nPosBracket);
     839             :                 }
     840          31 :                 int iMatchingStepIdx = 0;
     841          31 :                 Step *matchingStep = nullptr;
     842         125 :                 for (auto &step : steps)
     843             :                 {
     844          97 :                     if (step.alg->GetName() == stepName)
     845             :                     {
     846          33 :                         if (iRequestedStepIdx >= 0)
     847             :                         {
     848           5 :                             if (iRequestedStepIdx == iMatchingStepIdx)
     849             :                             {
     850           2 :                                 matchingStep = &step;
     851           2 :                                 break;
     852             :                             }
     853           3 :                             ++iMatchingStepIdx;
     854             :                         }
     855          28 :                         else if (matchingStep == nullptr)
     856             :                         {
     857          27 :                             matchingStep = &step;
     858             :                         }
     859             :                         else
     860             :                         {
     861           2 :                             ReportError(
     862             :                                 CE_Failure, CPLE_AppDefined,
     863             :                                 "Argument '%s' is ambiguous as there are "
     864             :                                 "several '%s' steps in the pipeline. Qualify "
     865             :                                 "it as '%s[<zero-based-index>]' to remove "
     866             :                                 "ambiguity.",
     867           1 :                                 arg->GetName().c_str(), stepName.c_str(),
     868             :                                 stepName.c_str());
     869           1 :                             return false;
     870             :                         }
     871             :                     }
     872             :                 }
     873          30 :                 if (!matchingStep)
     874             :                 {
     875           4 :                     ReportError(CE_Failure, CPLE_AppDefined,
     876             :                                 "Argument '%s' refers to a non-existing '%s' "
     877             :                                 "step in the pipeline.",
     878           2 :                                 arg->GetName().c_str(), tokens[0]);
     879           2 :                     return false;
     880             :                 }
     881             : 
     882          28 :                 auto &step = *matchingStep;
     883             :                 std::string stepArgNameDashDash =
     884          84 :                     std::string("--").append(stepArgName);
     885             : 
     886          56 :                 auto oKeyPair = std::make_pair(matchingStep, stepArgName);
     887          28 :                 if (!cpl::contains(alreadyCleanedArgs, oKeyPair))
     888             :                 {
     889          28 :                     alreadyCleanedArgs.insert(std::move(oKeyPair));
     890             : 
     891          56 :                     std::vector<GDALAlgorithmArg *> positionalArgs;
     892         302 :                     for (auto &stepArg : step.alg->GetArgs())
     893             :                     {
     894         274 :                         if (stepArg->IsPositional())
     895          22 :                             positionalArgs.push_back(stepArg.get());
     896             :                     }
     897             : 
     898             :                     // Remove step arguments that match the user override
     899             :                     const std::string stepArgNameDashDashEqual =
     900          56 :                         stepArgNameDashDash + '=';
     901          28 :                     size_t idxPositional = 0;
     902          40 :                     for (auto oIter = step.args.begin();
     903          40 :                          oIter != step.args.end();)
     904             :                     {
     905          12 :                         const auto &iterArgName = *oIter;
     906          12 :                         if (iterArgName == stepArgNameDashDash)
     907             :                         {
     908           1 :                             oIter = step.args.erase(oIter);
     909           1 :                             auto stepArg = step.alg->GetArg(stepArgName);
     910           1 :                             if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
     911             :                             {
     912           1 :                                 if (oIter != step.args.end())
     913           1 :                                     oIter = step.args.erase(oIter);
     914             :                             }
     915             :                         }
     916          11 :                         else if (cpl::starts_with(iterArgName,
     917             :                                                   stepArgNameDashDashEqual))
     918             :                         {
     919           3 :                             oIter = step.args.erase(oIter);
     920             :                         }
     921           8 :                         else if (!iterArgName.empty() && iterArgName[0] == '-')
     922             :                         {
     923           3 :                             const auto equalPos = iterArgName.find('=');
     924           6 :                             auto stepArg = step.alg->GetArg(
     925             :                                 equalPos == std::string::npos
     926           6 :                                     ? iterArgName
     927             :                                     : iterArgName.substr(0, equalPos));
     928           3 :                             ++oIter;
     929           3 :                             if (stepArg && equalPos == std::string::npos &&
     930           0 :                                 stepArg->GetType() != GAAT_BOOLEAN)
     931             :                             {
     932           0 :                                 if (oIter != step.args.end())
     933           0 :                                     ++oIter;
     934             :                             }
     935             :                         }
     936           5 :                         else if (idxPositional < positionalArgs.size())
     937             :                         {
     938           4 :                             if (positionalArgs[idxPositional]->GetName() ==
     939             :                                 stepArgName)
     940             :                             {
     941           2 :                                 oIter = step.args.erase(oIter);
     942             :                             }
     943             :                             else
     944             :                             {
     945           2 :                                 ++oIter;
     946             :                             }
     947           4 :                             ++idxPositional;
     948             :                         }
     949             :                         else
     950             :                         {
     951           1 :                             ++oIter;
     952             :                         }
     953             :                     }
     954             :                 }
     955             : 
     956          28 :                 if (arg->IsUserProvided())
     957             :                 {
     958             :                     // Add user override
     959          10 :                     step.args.push_back(std::move(stepArgNameDashDash));
     960          10 :                     auto stepArg = step.alg->GetArg(stepArgName);
     961          10 :                     if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
     962             :                     {
     963           8 :                         step.args.push_back(arg->Get<std::string>());
     964             :                     }
     965             :                 }
     966             :             }
     967             :         }
     968             :     }
     969             : 
     970         235 :     int nInitialDatasetType = 0;
     971         235 :     if (bIsGenericPipeline)
     972             :     {
     973          80 :         if (!m_bExpectReadStep)
     974             :         {
     975          10 :             CPLAssert(m_inputDataset.size() == 1 &&
     976             :                       m_inputDataset[0].GetDatasetRef());
     977          10 :             if (m_inputDataset[0].GetDatasetRef()->GetRasterCount() > 0)
     978             :             {
     979           5 :                 nInitialDatasetType = GDAL_OF_RASTER;
     980             :             }
     981           5 :             else if (m_inputDataset[0].GetDatasetRef()->GetLayerCount() > 0)
     982             :             {
     983           5 :                 nInitialDatasetType = GDAL_OF_VECTOR;
     984             :             }
     985             :         }
     986             : 
     987             :         // Parse each step, but without running the validation
     988          80 :         int nDatasetType = nInitialDatasetType;
     989          80 :         bool firstStep = nDatasetType == 0;
     990             : 
     991         270 :         for (auto &step : steps)
     992             :         {
     993         195 :             bool ret = false;
     994         195 :             CPLErrorAccumulator oAccumulator;
     995         195 :             bool hasTriedRaster = false;
     996         195 :             if (nDatasetType == 0 || nDatasetType == GDAL_OF_RASTER)
     997             :             {
     998         178 :                 hasTriedRaster = true;
     999             :                 [[maybe_unused]] auto context =
    1000         356 :                     oAccumulator.InstallForCurrentScope();
    1001         178 :                 step.alg->m_skipValidationInParseCommandLine = true;
    1002         178 :                 ret = step.alg->ParseCommandLineArguments(step.args);
    1003         178 :                 if (ret && nDatasetType == 0 && forAutoComplete)
    1004             :                 {
    1005           7 :                     ret = step.alg->ValidateArguments();
    1006          13 :                     if (ret && firstStep &&
    1007           6 :                         step.alg->m_inputDataset.size() == 1)
    1008             :                     {
    1009           6 :                         auto poDS = step.alg->m_inputDataset[0].GetDatasetRef();
    1010           6 :                         if (poDS && poDS->GetLayerCount() > 0)
    1011           3 :                             ret = false;
    1012             :                     }
    1013         178 :                 }
    1014             :             }
    1015          25 :             else if (!m_bExpectReadStep &&
    1016           8 :                      nDatasetType == step.alg->GetInputType())
    1017             :             {
    1018           3 :                 step.alg->m_skipValidationInParseCommandLine = true;
    1019           3 :                 ret = step.alg->ParseCommandLineArguments(step.args);
    1020           3 :                 if (!ret)
    1021           1 :                     return false;
    1022             :             }
    1023             : 
    1024         194 :             if (!ret)
    1025             :             {
    1026             :                 auto algVector =
    1027          27 :                     GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
    1028          44 :                 if (algVector &&
    1029          44 :                     (nDatasetType == 0 || nDatasetType == GDAL_OF_VECTOR))
    1030             :                 {
    1031          23 :                     step.alg = std::move(algVector);
    1032          23 :                     step.alg->m_skipValidationInParseCommandLine = true;
    1033          23 :                     ret = step.alg->ParseCommandLineArguments(step.args);
    1034          23 :                     if (ret)
    1035             :                     {
    1036          32 :                         step.alg->SetCallPath({step.alg->GetName()});
    1037          16 :                         step.alg->SetReferencePathForRelativePaths(
    1038             :                             GetReferencePathForRelativePaths());
    1039          16 :                         step.alreadyChangedType = true;
    1040             :                     }
    1041           7 :                     else if (!forAutoComplete)
    1042           3 :                         return false;
    1043             :                 }
    1044          24 :                 if (!ret && hasTriedRaster && !forAutoComplete)
    1045             :                 {
    1046           2 :                     for (const auto &sError : oAccumulator.GetErrors())
    1047             :                     {
    1048           1 :                         CPLError(sError.type, sError.no, "%s",
    1049             :                                  sError.msg.c_str());
    1050             :                     }
    1051           1 :                     return false;
    1052             :                 }
    1053             :             }
    1054         190 :             if (ret && forAutoComplete)
    1055          15 :                 nDatasetType = step.alg->GetOutputType();
    1056         190 :             firstStep = false;
    1057             :         }
    1058             :     }
    1059             :     else
    1060             :     {
    1061         516 :         for (auto &step : steps)
    1062             :         {
    1063         374 :             step.alg->m_skipValidationInParseCommandLine = true;
    1064         374 :             if (!step.alg->ParseCommandLineArguments(step.args))
    1065          13 :                 return false;
    1066             :         }
    1067             :     }
    1068             : 
    1069             :     // Evaluate "input" argument of "read" step, together with the "output"
    1070             :     // argument of the "write" step, in case they point to the same dataset.
    1071         217 :     auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
    1072         421 :     if (inputArg && inputArg->IsExplicitlySet() &&
    1073         638 :         inputArg->GetType() == GAAT_DATASET_LIST &&
    1074         208 :         inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
    1075             :     {
    1076         204 :         steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
    1077             :     }
    1078             : 
    1079         217 :     if (bIsGenericPipeline)
    1080             :     {
    1081          75 :         int nLastStepOutputType = nInitialDatasetType;
    1082          75 :         if (m_bExpectReadStep)
    1083             :         {
    1084          66 :             nLastStepOutputType = GDAL_OF_VECTOR;
    1085          66 :             if (steps.front().alg->GetName() !=
    1086         133 :                     std::string(GDALRasterReadAlgorithm::NAME) &&
    1087           1 :                 steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
    1088             :             {
    1089           1 :                 nLastStepOutputType = GDAL_OF_RASTER;
    1090             :             }
    1091             :             else
    1092             :             {
    1093          65 :                 auto &inputDatasets = steps.front().alg->GetInputDatasets();
    1094          65 :                 if (!inputDatasets.empty())
    1095             :                 {
    1096          65 :                     auto poSrcDS = inputDatasets[0].GetDatasetRef();
    1097          65 :                     if (poSrcDS)
    1098             :                     {
    1099          63 :                         if (poSrcDS->GetRasterCount() != 0)
    1100          40 :                             nLastStepOutputType = GDAL_OF_RASTER;
    1101             :                     }
    1102             :                 }
    1103             :             }
    1104             :         }
    1105             : 
    1106          75 :         for (size_t i = (m_bExpectReadStep ? 1 : 0);
    1107         175 :              !forAutoComplete && i < steps.size(); ++i)
    1108             :         {
    1109         197 :             if (!steps[i].alreadyChangedType && !steps[i].isSubAlgorithm &&
    1110         197 :                 GetStepAlg(steps[i].alg->GetName()) == nullptr)
    1111             :             {
    1112          77 :                 auto newAlg = GetStepAlg(steps[i].alg->GetName() +
    1113             :                                          (nLastStepOutputType == GDAL_OF_RASTER
    1114             :                                               ? RASTER_SUFFIX
    1115          77 :                                               : VECTOR_SUFFIX));
    1116          77 :                 CPLAssert(newAlg);
    1117             : 
    1118          77 :                 if (steps[i].alg->GetName() ==
    1119             :                     GDALTeeStepAlgorithmAbstract::NAME)
    1120             :                 {
    1121             :                     const auto poSrcTeeAlg =
    1122          14 :                         dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(
    1123          28 :                             steps[i].alg.get());
    1124             :                     auto poDstTeeAlg =
    1125          14 :                         dynamic_cast<GDALTeeStepAlgorithmAbstract *>(
    1126          28 :                             newAlg.get());
    1127          14 :                     CPLAssert(poSrcTeeAlg);
    1128          14 :                     CPLAssert(poDstTeeAlg);
    1129          14 :                     poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
    1130             :                 }
    1131             : 
    1132          77 :                 steps[i].alg = std::move(newAlg);
    1133             : 
    1134          77 :                 if (i == steps.size() - 1 && m_bExpectWriteStep)
    1135             :                 {
    1136          56 :                     SetWriteArgFromPipeline();
    1137             :                 }
    1138             : 
    1139          77 :                 steps[i].alg->m_skipValidationInParseCommandLine = true;
    1140          77 :                 if (!steps[i].alg->ParseCommandLineArguments(steps[i].args))
    1141           1 :                     return false;
    1142         152 :                 steps[i].alg->SetCallPath({steps[i].alg->GetName()});
    1143          76 :                 steps[i].alg->SetReferencePathForRelativePaths(
    1144             :                     GetReferencePathForRelativePaths());
    1145          76 :                 if (IsCalledFromCommandLine())
    1146          17 :                     steps[i].alg->SetCalledFromCommandLine();
    1147          76 :                 steps[i].alreadyChangedType = true;
    1148             :             }
    1149          48 :             else if (i > 0 &&
    1150          22 :                      steps[i].alg->GetInputType() != nLastStepOutputType)
    1151             :             {
    1152           9 :                 ReportError(
    1153             :                     CE_Failure, CPLE_AppDefined,
    1154             :                     "Step '%s' expects a %s input dataset, but "
    1155             :                     "previous step '%s' "
    1156             :                     "generates a %s output dataset",
    1157           2 :                     steps[i].alg->GetName().c_str(),
    1158           2 :                     steps[i].alg->GetInputType() == GDAL_OF_RASTER ? "raster"
    1159           1 :                     : steps[i].alg->GetInputType() == GDAL_OF_VECTOR
    1160           1 :                         ? "vector"
    1161             :                         : "unknown",
    1162           2 :                     steps[i - 1].alg->GetName().c_str(),
    1163             :                     nLastStepOutputType == GDAL_OF_RASTER   ? "raster"
    1164           1 :                     : nLastStepOutputType == GDAL_OF_VECTOR ? "vector"
    1165             :                                                             : "unknown");
    1166           2 :                 return false;
    1167             :             }
    1168         100 :             nLastStepOutputType = steps[i].alg->GetOutputType();
    1169             :         }
    1170             :     }
    1171             : 
    1172         723 :     for (const auto &step : steps)
    1173             :     {
    1174         521 :         if (!step.alg->ValidateArguments() && !forAutoComplete)
    1175          12 :             return false;
    1176             :     }
    1177             : 
    1178         703 :     for (auto &step : steps)
    1179         501 :         m_steps.push_back(std::move(step.alg));
    1180             : 
    1181         202 :     return true;
    1182             : }
    1183             : 
    1184             : /************************************************************************/
    1185             : /*           GDALAbstractPipelineAlgorithm::BuildNestedPipeline()       */
    1186             : /************************************************************************/
    1187             : 
    1188          25 : std::string GDALAbstractPipelineAlgorithm::BuildNestedPipeline(
    1189             :     GDALPipelineStepAlgorithm *curAlg,
    1190             :     std::vector<std::string> &nestedPipelineArgs, bool forAutoComplete)
    1191             : {
    1192          25 :     std::string datasetNameOut;
    1193          25 :     CPLAssert(curAlg);
    1194             : 
    1195          50 :     auto nestedPipeline = CreateNestedPipeline();
    1196          25 :     if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
    1197          17 :         nestedPipeline->m_bExpectReadStep = false;
    1198             :     else
    1199           8 :         nestedPipeline->m_bExpectWriteStep = false;
    1200          25 :     nestedPipeline->m_executionForStreamOutput = m_executionForStreamOutput;
    1201          25 :     nestedPipeline->SetReferencePathForRelativePaths(
    1202             :         GetReferencePathForRelativePaths());
    1203             : 
    1204          50 :     std::string argsStr = OPEN_NESTED_PIPELINE;
    1205         101 :     for (const std::string &str : nestedPipelineArgs)
    1206             :     {
    1207          76 :         argsStr += ' ';
    1208          76 :         argsStr += GDALAlgorithmArg::GetEscapedString(str);
    1209             :     }
    1210          25 :     argsStr += ' ';
    1211          25 :     argsStr += CLOSE_NESTED_PIPELINE;
    1212             : 
    1213          25 :     if (curAlg->GetName() != GDALTeeStepAlgorithmAbstract::NAME)
    1214             :     {
    1215           8 :         if (!nestedPipeline->ParseCommandLineArguments(nestedPipelineArgs,
    1216          12 :                                                        forAutoComplete) ||
    1217           4 :             (!forAutoComplete && !nestedPipeline->Run()))
    1218             :         {
    1219           5 :             return datasetNameOut;
    1220             :         }
    1221           3 :         auto poDS = nestedPipeline->GetOutputDataset().GetDatasetRef();
    1222           3 :         if (!poDS)
    1223             :         {
    1224             :             // That shouldn't happen normally for well-behaved algorithms, but
    1225             :             // it doesn't hurt checking.
    1226           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    1227             :                         "Nested pipeline does not generate an output dataset");
    1228           0 :             return datasetNameOut;
    1229             :         }
    1230             :         datasetNameOut =
    1231           3 :             CPLSPrintf("$$nested_pipeline_%p$$", nestedPipeline.get());
    1232           3 :         curAlg->m_oMapDatasetNameToDataset[datasetNameOut] = poDS;
    1233             : 
    1234           3 :         poDS->SetDescription(argsStr.c_str());
    1235             :     }
    1236             : 
    1237          20 :     m_apoNestedPipelines.emplace_back(std::move(nestedPipeline));
    1238             : 
    1239          20 :     if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
    1240             :     {
    1241          17 :         auto teeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(curAlg);
    1242          17 :         if (teeAlg)
    1243             :         {
    1244          17 :             datasetNameOut = std::move(argsStr);
    1245          17 :             if (!teeAlg->BindFilename(datasetNameOut,
    1246          17 :                                       m_apoNestedPipelines.back().get(),
    1247             :                                       nestedPipelineArgs))
    1248             :             {
    1249           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
    1250             :                             "Another identical nested pipeline exists");
    1251           1 :                 datasetNameOut.clear();
    1252             :             }
    1253             :         }
    1254             :     }
    1255             : 
    1256          20 :     nestedPipelineArgs.clear();
    1257             : 
    1258          20 :     return datasetNameOut;
    1259             : }
    1260             : 
    1261             : /************************************************************************/
    1262             : /*         GDALAbstractPipelineAlgorithm::GetAutoComplete()             */
    1263             : /************************************************************************/
    1264             : 
    1265             : std::vector<std::string>
    1266          46 : GDALAbstractPipelineAlgorithm::GetAutoComplete(std::vector<std::string> &args,
    1267             :                                                bool lastWordIsComplete,
    1268             :                                                bool showAllOptions)
    1269             : {
    1270             :     {
    1271          92 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    1272          46 :         ParseCommandLineArguments(args, /*forAutoComplete=*/true);
    1273             :     }
    1274             :     VSIStatBufL sStat;
    1275          52 :     if (!m_pipeline.empty() && VSIStatL(m_pipeline.c_str(), &sStat) == 0 &&
    1276          52 :         !m_steps.empty() && !args.empty())
    1277             :     {
    1278          12 :         std::map<std::string, std::vector<GDALAlgorithm *>> mapSteps;
    1279          26 :         for (const auto &step : m_steps)
    1280             :         {
    1281          20 :             mapSteps[step->GetName()].push_back(step.get());
    1282             :         }
    1283             : 
    1284          12 :         std::vector<std::string> ret;
    1285           6 :         const auto &lastArg = args.back();
    1286          18 :         if (!lastArg.empty() && lastArg[0] == '-' &&
    1287          18 :             lastArg.find('=') == std::string::npos && !lastWordIsComplete)
    1288             :         {
    1289          13 :             for (const auto &step : m_steps)
    1290             :             {
    1291             :                 const int iterCount =
    1292          10 :                     static_cast<int>(mapSteps[step->GetName()].size());
    1293          22 :                 for (int i = 0; i < iterCount; ++i)
    1294             :                 {
    1295         148 :                     for (const auto &arg : step->GetArgs())
    1296             :                     {
    1297         259 :                         if (!arg->IsHiddenForCLI() &&
    1298         123 :                             arg->GetCategory() != GAAC_COMMON)
    1299             :                         {
    1300         174 :                             std::string s = std::string("--");
    1301         174 :                             if (!((step->GetName() == "read" &&
    1302          11 :                                    IsReadSpecificArgument(
    1303          11 :                                        arg->GetName().c_str())) ||
    1304          76 :                                   (step->GetName() == "write" &&
    1305          29 :                                    IsWriteSpecificArgument(
    1306          29 :                                        arg->GetName().c_str()))))
    1307             :                             {
    1308          53 :                                 s += step->GetName();
    1309          53 :                                 if (iterCount > 1)
    1310             :                                 {
    1311          32 :                                     s += '[';
    1312          32 :                                     s += std::to_string(i);
    1313          32 :                                     s += ']';
    1314             :                                 }
    1315          53 :                                 s += '.';
    1316             :                             }
    1317          87 :                             s += arg->GetName();
    1318          87 :                             if (arg->GetType() == GAAT_BOOLEAN)
    1319          19 :                                 ret.push_back(std::move(s));
    1320             :                             else
    1321          68 :                                 ret.push_back(s + "=");
    1322             :                         }
    1323             :                     }
    1324             :                 }
    1325             :             }
    1326             :         }
    1327           6 :         else if (cpl::starts_with(lastArg, "--") &&
    1328           6 :                  lastArg.find('=') != std::string::npos && !lastWordIsComplete)
    1329             :         {
    1330           3 :             const auto nDotPos = lastArg.find('.');
    1331           6 :             std::string stepName;
    1332           6 :             std::string argName;
    1333           3 :             int idx = 0;
    1334           3 :             if (nDotPos != std::string::npos)
    1335             :             {
    1336           1 :                 stepName = lastArg.substr(strlen("--"), nDotPos - strlen("--"));
    1337           1 :                 const auto nBracketPos = stepName.find('[');
    1338           1 :                 if (nBracketPos != std::string::npos)
    1339             :                 {
    1340           1 :                     idx = atoi(stepName.c_str() + nBracketPos + 1);
    1341           1 :                     stepName.resize(nBracketPos);
    1342             :                 }
    1343           1 :                 argName = "--" + lastArg.substr(nDotPos + 1);
    1344             :             }
    1345             :             else
    1346             :             {
    1347           2 :                 argName = lastArg;
    1348           7 :                 for (const char *prefix : apszReadParametersPrefixOmitted)
    1349             :                 {
    1350           6 :                     if (cpl::starts_with(lastArg.substr(strlen("--")),
    1351          12 :                                          std::string(prefix) + "="))
    1352             :                     {
    1353           1 :                         stepName = "read";
    1354           1 :                         break;
    1355             :                     }
    1356             :                 }
    1357             : 
    1358          13 :                 for (const char *prefix : apszWriteParametersPrefixOmitted)
    1359             :                 {
    1360          12 :                     if (cpl::starts_with(lastArg.substr(strlen("--")),
    1361          24 :                                          std::string(prefix) + "="))
    1362             :                     {
    1363           1 :                         stepName = "write";
    1364           1 :                         break;
    1365             :                     }
    1366             :                 }
    1367             :             }
    1368             : 
    1369           3 :             auto iter = mapSteps.find(stepName);
    1370           6 :             if (iter != mapSteps.end() && idx >= 0 &&
    1371           3 :                 static_cast<size_t>(idx) < iter->second.size())
    1372             :             {
    1373           3 :                 auto &step = iter->second[idx];
    1374           3 :                 std::vector<std::string> subArgs;
    1375          32 :                 for (const auto &arg : step->GetArgs())
    1376             :                 {
    1377          58 :                     std::string strArg;
    1378          33 :                     if (arg->IsExplicitlySet() &&
    1379           4 :                         arg->Serialize(strArg, /* absolutePath=*/false))
    1380             :                     {
    1381           4 :                         subArgs.push_back(std::move(strArg));
    1382             :                     }
    1383             :                 }
    1384           3 :                 subArgs.push_back(std::move(argName));
    1385           3 :                 ret = step->GetAutoComplete(subArgs, lastWordIsComplete,
    1386           3 :                                             showAllOptions);
    1387             :             }
    1388             :         }
    1389           6 :         return ret;
    1390             :     }
    1391             :     else
    1392             :     {
    1393          80 :         std::vector<std::string> ret;
    1394          80 :         std::set<std::string> setSuggestions;
    1395          40 :         if (args.size() <= 1)
    1396             :         {
    1397         324 :             for (const std::string &name : GetStepRegistry().GetNames())
    1398             :             {
    1399         318 :                 auto alg = GetStepRegistry().Instantiate(name);
    1400             :                 auto stepAlg =
    1401         318 :                     dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
    1402         318 :                 if (stepAlg && stepAlg->CanBeFirstStep())
    1403             :                 {
    1404             :                     std::string suggestionName =
    1405          33 :                         CPLString(name)
    1406          66 :                             .replaceAll(RASTER_SUFFIX, "")
    1407          66 :                             .replaceAll(VECTOR_SUFFIX, "");
    1408          33 :                     if (!cpl::contains(setSuggestions, suggestionName))
    1409             :                     {
    1410          31 :                         if (!args.empty() && suggestionName == args[0])
    1411           3 :                             return {};
    1412          45 :                         if (args.empty() ||
    1413          17 :                             cpl::starts_with(suggestionName, args[0]))
    1414             :                         {
    1415          14 :                             setSuggestions.insert(suggestionName);
    1416          14 :                             ret.push_back(std::move(suggestionName));
    1417             :                         }
    1418             :                     }
    1419             :                 }
    1420             :             }
    1421             :         }
    1422             :         else
    1423             :         {
    1424          31 :             int nDatasetType = GetInputType();
    1425          31 :             constexpr int MIXED_TYPE = GDAL_OF_RASTER | GDAL_OF_VECTOR;
    1426          31 :             const bool isMixedTypePipeline = nDatasetType == MIXED_TYPE;
    1427          62 :             std::string lastStep = args[0];
    1428          62 :             std::vector<std::string> lastArgs;
    1429          31 :             bool firstStep = true;
    1430         101 :             for (size_t i = 1; i < args.size(); ++i)
    1431             :             {
    1432          53 :                 if (firstStep && isMixedTypePipeline &&
    1433         142 :                     nDatasetType == MIXED_TYPE && !args[i].empty() &&
    1434          19 :                     args[i][0] != '-')
    1435             :                 {
    1436          34 :                     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
    1437             :                     auto poDS = std::unique_ptr<GDALDataset>(
    1438          34 :                         GDALDataset::Open(args[i].c_str()));
    1439          19 :                     if (poDS && poDS->GetLayerCount() > 0 &&
    1440           2 :                         poDS->GetRasterCount() == 0)
    1441             :                     {
    1442           2 :                         nDatasetType = GDAL_OF_VECTOR;
    1443             :                     }
    1444          17 :                     else if (poDS && poDS->GetLayerCount() == 0 &&
    1445           2 :                              (poDS->GetRasterCount() > 0 ||
    1446           0 :                               poDS->GetMetadata("SUBDATASETS") != nullptr))
    1447             :                     {
    1448           2 :                         nDatasetType = GDAL_OF_RASTER;
    1449             :                     }
    1450             :                 }
    1451          70 :                 lastArgs.push_back(args[i]);
    1452          70 :                 if (i + 1 < args.size() && args[i] == "!")
    1453             :                 {
    1454          20 :                     firstStep = false;
    1455          20 :                     ++i;
    1456          20 :                     lastArgs.clear();
    1457          20 :                     lastStep = args[i];
    1458          40 :                     auto curAlg = GetStepAlg(lastStep);
    1459          20 :                     if (isMixedTypePipeline && !curAlg)
    1460             :                     {
    1461           9 :                         if (nDatasetType == GDAL_OF_RASTER)
    1462           1 :                             curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
    1463           8 :                         else if (nDatasetType == GDAL_OF_VECTOR)
    1464           1 :                             curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
    1465             :                     }
    1466          20 :                     if (curAlg)
    1467          10 :                         nDatasetType = curAlg->GetOutputType();
    1468             :                 }
    1469             :             }
    1470             : 
    1471          58 :             if (args.back() == "!" ||
    1472          58 :                 (args[args.size() - 2] == "!" && !GetStepAlg(args.back()) &&
    1473          34 :                  !GetStepAlg(args.back() + RASTER_SUFFIX) &&
    1474          34 :                  !GetStepAlg(args.back() + VECTOR_SUFFIX)))
    1475             :             {
    1476         450 :                 for (const std::string &name : GetStepRegistry().GetNames())
    1477             :                 {
    1478         440 :                     auto alg = GetStepRegistry().Instantiate(name);
    1479             :                     auto stepAlg =
    1480         440 :                         dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
    1481         440 :                     if (stepAlg && isMixedTypePipeline &&
    1482         880 :                         nDatasetType != MIXED_TYPE &&
    1483         130 :                         stepAlg->GetInputType() != nDatasetType)
    1484             :                     {
    1485          65 :                         continue;
    1486             :                     }
    1487         375 :                     if (stepAlg && !stepAlg->CanBeFirstStep())
    1488             :                     {
    1489             :                         std::string suggestionName =
    1490         339 :                             CPLString(name)
    1491         678 :                                 .replaceAll(RASTER_SUFFIX, "")
    1492        1017 :                                 .replaceAll(VECTOR_SUFFIX, "");
    1493         339 :                         if (!cpl::contains(setSuggestions, suggestionName))
    1494             :                         {
    1495         323 :                             setSuggestions.insert(suggestionName);
    1496         323 :                             ret.push_back(std::move(suggestionName));
    1497             :                         }
    1498             :                     }
    1499             :                 }
    1500             :             }
    1501             :             else
    1502             :             {
    1503          42 :                 auto curAlg = GetStepAlg(lastStep);
    1504          21 :                 if (isMixedTypePipeline && !curAlg)
    1505             :                 {
    1506           9 :                     if (nDatasetType == GDAL_OF_RASTER)
    1507           1 :                         curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
    1508           8 :                     else if (nDatasetType == GDAL_OF_VECTOR)
    1509           1 :                         curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
    1510             :                     else
    1511             :                     {
    1512          14 :                         for (const char *suffix :
    1513          21 :                              {RASTER_SUFFIX, VECTOR_SUFFIX})
    1514             :                         {
    1515          14 :                             curAlg = GetStepAlg(lastStep + suffix);
    1516          14 :                             if (curAlg)
    1517             :                             {
    1518          99 :                                 for (const auto &v : curAlg->GetAutoComplete(
    1519             :                                          lastArgs, lastWordIsComplete,
    1520         184 :                                          /* showAllOptions = */ false))
    1521             :                                 {
    1522          85 :                                     if (!cpl::contains(setSuggestions, v))
    1523             :                                     {
    1524          70 :                                         setSuggestions.insert(v);
    1525          70 :                                         ret.push_back(std::move(v));
    1526             :                                     }
    1527             :                                 }
    1528             :                             }
    1529             :                         }
    1530           7 :                         curAlg.reset();
    1531             :                     }
    1532             :                 }
    1533          21 :                 if (curAlg)
    1534             :                 {
    1535          28 :                     ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
    1536          14 :                                                   /* showAllOptions = */ false);
    1537             :                 }
    1538             :             }
    1539             :         }
    1540          37 :         return ret;
    1541             :     }
    1542             : }
    1543             : 
    1544             : /************************************************************************/
    1545             : /*            GDALAbstractPipelineAlgorithm::SaveGDALGFile()            */
    1546             : /************************************************************************/
    1547             : 
    1548          12 : bool GDALAbstractPipelineAlgorithm::SaveGDALGFile(
    1549             :     const std::string &outFilename, std::string &outString) const
    1550             : {
    1551          24 :     std::string osCommandLine;
    1552             : 
    1553          44 :     for (const auto &path : GDALAlgorithm::m_callPath)
    1554             :     {
    1555          32 :         if (!osCommandLine.empty())
    1556          20 :             osCommandLine += ' ';
    1557          32 :         osCommandLine += path;
    1558             :     }
    1559             : 
    1560             :     // Do not include the last step
    1561          32 :     for (size_t i = 0; i + 1 < m_steps.size(); ++i)
    1562             :     {
    1563          21 :         const auto &step = m_steps[i];
    1564          21 :         if (!step->IsNativelyStreamingCompatible())
    1565             :         {
    1566           3 :             GDALAlgorithm::ReportError(
    1567             :                 CE_Warning, CPLE_AppDefined,
    1568             :                 "Step %s is not natively streaming compatible, and "
    1569             :                 "may cause significant processing time at opening",
    1570           3 :                 step->GDALAlgorithm::GetName().c_str());
    1571             :         }
    1572             : 
    1573          21 :         if (i > 0)
    1574           9 :             osCommandLine += " !";
    1575          43 :         for (const auto &path : step->GDALAlgorithm::m_callPath)
    1576             :         {
    1577          22 :             if (!osCommandLine.empty())
    1578          22 :                 osCommandLine += ' ';
    1579          22 :             osCommandLine += path;
    1580             :         }
    1581             : 
    1582         221 :         for (const auto &arg : step->GetArgs())
    1583             :         {
    1584         201 :             if (arg->IsExplicitlySet())
    1585             :             {
    1586          19 :                 osCommandLine += ' ';
    1587          19 :                 std::string strArg;
    1588          19 :                 if (!arg->Serialize(strArg, /* absolutePath=*/false))
    1589             :                 {
    1590           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1591             :                              "Cannot serialize argument %s",
    1592           1 :                              arg->GetName().c_str());
    1593           1 :                     return false;
    1594             :                 }
    1595          18 :                 osCommandLine += strArg;
    1596             :             }
    1597             :         }
    1598             :     }
    1599             : 
    1600          11 :     return GDALAlgorithm::SaveGDALG(outFilename, outString, osCommandLine);
    1601             : }
    1602             : 
    1603             : /************************************************************************/
    1604             : /*              GDALAbstractPipelineAlgorithm::RunStep()                */
    1605             : /************************************************************************/
    1606             : 
    1607         227 : bool GDALAbstractPipelineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
    1608             : {
    1609         227 :     if (m_stepOnWhichHelpIsRequested)
    1610             :     {
    1611           4 :         printf(
    1612             :             "%s",
    1613           8 :             m_stepOnWhichHelpIsRequested->GetUsageForCLI(false).c_str()); /*ok*/
    1614           4 :         return true;
    1615             :     }
    1616             : 
    1617         223 :     if (m_steps.empty())
    1618             :     {
    1619             :         // If invoked programmatically, not from the command line.
    1620             : 
    1621         118 :         if (m_pipeline.empty())
    1622             :         {
    1623           2 :             ReportError(CE_Failure, CPLE_AppDefined,
    1624             :                         "'pipeline' argument not set");
    1625          29 :             return false;
    1626             :         }
    1627             : 
    1628         116 :         const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
    1629         116 :         if (!ParseCommandLineArguments(aosTokens))
    1630          27 :             return false;
    1631             :     }
    1632             : 
    1633             :     // Handle output to GDALG file
    1634         194 :     if (!m_steps.empty() && m_steps.back()->GetName() == "write")
    1635             :     {
    1636         168 :         if (m_steps.back()->IsGDALGOutput())
    1637             :         {
    1638          11 :             const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
    1639             :             const auto &filename =
    1640          11 :                 outputArg->Get<GDALArgDatasetValue>().GetName();
    1641          11 :             const char *pszType = "";
    1642          11 :             if (GDALDoesFileOrDatasetExist(filename.c_str(), &pszType))
    1643             :             {
    1644             :                 const auto overwriteArg =
    1645           1 :                     m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
    1646           1 :                 if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
    1647             :                 {
    1648           1 :                     if (!overwriteArg->Get<bool>())
    1649             :                     {
    1650           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1651             :                                  "%s '%s' already exists. Specify the "
    1652             :                                  "--overwrite option to overwrite it.",
    1653             :                                  pszType, filename.c_str());
    1654           0 :                         return false;
    1655             :                     }
    1656             :                 }
    1657             :             }
    1658             : 
    1659          22 :             std::string outStringUnused;
    1660          11 :             return SaveGDALGFile(filename, outStringUnused);
    1661             :         }
    1662             : 
    1663             :         const auto outputFormatArg =
    1664         157 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
    1665         157 :         const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
    1666         314 :         if (outputArg && outputArg->GetType() == GAAT_DATASET &&
    1667         157 :             outputArg->IsExplicitlySet())
    1668             :         {
    1669         157 :             const auto &outputFile = outputArg->Get<GDALArgDatasetValue>();
    1670             :             bool isVRTOutput;
    1671         314 :             if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
    1672         157 :                 outputFormatArg->IsExplicitlySet())
    1673             :             {
    1674          81 :                 const auto &val = outputFormatArg->Get<std::string>();
    1675          81 :                 isVRTOutput = EQUAL(val.c_str(), "vrt");
    1676             :             }
    1677             :             else
    1678             :             {
    1679          76 :                 isVRTOutput = EQUAL(
    1680             :                     CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
    1681             :                     "vrt");
    1682             :             }
    1683         170 :             if (isVRTOutput && !outputFile.GetName().empty() &&
    1684          13 :                 m_steps.size() > 3)
    1685             :             {
    1686           1 :                 ReportError(
    1687             :                     CE_Failure, CPLE_NotSupported,
    1688             :                     "VRT output is not supported when there are more than 3 "
    1689             :                     "steps. Consider using the GDALG driver (files with "
    1690             :                     ".gdalg.json extension)");
    1691           1 :                 return false;
    1692             :             }
    1693         156 :             if (isVRTOutput)
    1694             :             {
    1695          24 :                 for (const auto &step : m_steps)
    1696             :                 {
    1697          24 :                     if (!step->m_outputVRTCompatible)
    1698             :                     {
    1699          12 :                         step->ReportError(
    1700             :                             CE_Failure, CPLE_NotSupported,
    1701             :                             "VRT output is not supported. Consider using the "
    1702             :                             "GDALG driver instead (files with .gdalg.json "
    1703             :                             "extension)");
    1704          12 :                         return false;
    1705             :                     }
    1706             :                 }
    1707             :             }
    1708             :         }
    1709             :     }
    1710             : 
    1711         208 :     if (m_executionForStreamOutput &&
    1712          38 :         !CPLTestBool(
    1713             :             CPLGetConfigOption("GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM", "NO")))
    1714             :     {
    1715             :         // For security reasons, to avoid that reading a .gdalg.json file writes
    1716             :         // a file on the file system.
    1717         111 :         for (const auto &step : m_steps)
    1718             :         {
    1719          80 :             if (step->GetName() == "write")
    1720             :             {
    1721          32 :                 if (!EQUAL(step->m_format.c_str(), "stream"))
    1722             :                 {
    1723           2 :                     ReportError(CE_Failure, CPLE_AppDefined,
    1724             :                                 "in streamed execution, --format "
    1725             :                                 "stream should be used");
    1726           5 :                     return false;
    1727             :                 }
    1728             :             }
    1729          48 :             else if (step->GeneratesFilesFromUserInput())
    1730             :             {
    1731           3 :                 ReportError(CE_Failure, CPLE_AppDefined,
    1732             :                             "Step '%s' not allowed in stream execution, unless "
    1733             :                             "the GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM "
    1734             :                             "configuration option is set.",
    1735           3 :                             step->GetName().c_str());
    1736           3 :                 return false;
    1737             :             }
    1738             :         }
    1739             :     }
    1740             : 
    1741             :     // Because of multiprocessing in gdal raster tile, make sure that all
    1742             :     // steps before it are serialized in a .gdal.json file
    1743         160 :     if (m_steps.size() >= 2 && m_steps.back()->SupportsInputMultiThreading() &&
    1744           2 :         m_steps.back()
    1745           2 :                 ->GetArg(GDAL_ARG_NAME_NUM_THREADS_INT_HIDDEN)
    1746         329 :                 ->Get<int>() > 1 &&
    1747           4 :         !(m_steps.size() == 2 && m_steps[0]->GetName() == "read"))
    1748             :     {
    1749           2 :         bool ret = false;
    1750           2 :         auto poSrcDS = m_inputDataset.size() == 1
    1751           2 :                            ? m_inputDataset[0].GetDatasetRef()
    1752           2 :                            : nullptr;
    1753           2 :         if (poSrcDS)
    1754             :         {
    1755           1 :             auto poSrcDriver = poSrcDS->GetDriver();
    1756           1 :             if (!poSrcDriver || EQUAL(poSrcDriver->GetDescription(), "MEM"))
    1757             :             {
    1758           1 :                 ReportError(
    1759             :                     CE_Failure, CPLE_AppDefined,
    1760             :                     "Cannot execute this pipeline in parallel mode due to "
    1761             :                     "input dataset being a non-materialized dataset. "
    1762             :                     "Materialize it first, or add '-j 1' to the last step "
    1763             :                     "'tile'");
    1764           1 :                 return false;
    1765             :             }
    1766             :         }
    1767           1 :         std::string outString;
    1768           1 :         if (SaveGDALGFile(std::string(), outString))
    1769             :         {
    1770           1 :             const char *const apszAllowedDrivers[] = {"GDALG", nullptr};
    1771           1 :             auto poCurDS = GDALDataset::Open(
    1772             :                 outString.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    1773             :                 apszAllowedDrivers);
    1774           1 :             if (poCurDS)
    1775             :             {
    1776           1 :                 auto &tileAlg = m_steps.back();
    1777           1 :                 tileAlg->m_inputDataset.clear();
    1778           1 :                 tileAlg->m_inputDataset.resize(1);
    1779           1 :                 tileAlg->m_inputDataset[0].Set(poCurDS);
    1780           1 :                 tileAlg->m_inputDataset[0].SetDatasetOpenedByAlgorithm();
    1781           1 :                 poCurDS->Release();
    1782           1 :                 ret = tileAlg->RunStep(ctxt);
    1783           1 :                 tileAlg->m_inputDataset[0].Close();
    1784             :             }
    1785             :         }
    1786           1 :         return ret;
    1787             :     }
    1788             : 
    1789         163 :     int countPipelinesWithProgress = 0;
    1790         384 :     for (size_t i = (m_bExpectReadStep ? 0 : 1); i < m_steps.size(); ++i)
    1791             :     {
    1792             :         const bool bCanHandleNextStep =
    1793         381 :             i < m_steps.size() - 1 &&
    1794         160 :             !m_steps[i]->CanHandleNextStep(m_steps[i + 1].get());
    1795         381 :         if (bCanHandleNextStep &&
    1796         160 :             !m_steps[i + 1]->IsNativelyStreamingCompatible())
    1797         113 :             ++countPipelinesWithProgress;
    1798         108 :         else if (!m_steps[i]->IsNativelyStreamingCompatible())
    1799          61 :             ++countPipelinesWithProgress;
    1800         221 :         if (bCanHandleNextStep)
    1801         160 :             ++i;
    1802             :     }
    1803         163 :     if (countPipelinesWithProgress == 0)
    1804          17 :         countPipelinesWithProgress = 1;
    1805             : 
    1806         163 :     bool ret = true;
    1807         163 :     GDALDataset *poCurDS = nullptr;
    1808         163 :     int iCurStepWithProgress = 0;
    1809             : 
    1810         163 :     if (!m_bExpectReadStep)
    1811             :     {
    1812           8 :         CPLAssert(m_inputDataset.size() == 1);
    1813           8 :         poCurDS = m_inputDataset[0].GetDatasetRef();
    1814           8 :         CPLAssert(poCurDS);
    1815             :     }
    1816             : 
    1817         163 :     GDALProgressFunc pfnProgress = ctxt.m_pfnProgress;
    1818         163 :     void *pProgressData = ctxt.m_pProgressData;
    1819         163 :     if (IsCalledFromCommandLine() && !m_steps.empty())
    1820             :     {
    1821             :         auto stepOutputStringArg =
    1822          15 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
    1823          15 :         if (stepOutputStringArg && stepOutputStringArg->IsOutput())
    1824             :         {
    1825           4 :             pfnProgress = nullptr;
    1826           4 :             pProgressData = nullptr;
    1827             :         }
    1828             :     }
    1829             : 
    1830         526 :     for (size_t i = 0; i < m_steps.size(); ++i)
    1831             :     {
    1832         377 :         auto &step = m_steps[i];
    1833         377 :         if (i > 0 || poCurDS)
    1834             :         {
    1835         222 :             if (!step->m_inputDataset.empty() &&
    1836           0 :                 step->m_inputDataset[0].GetDatasetRef())
    1837             :             {
    1838             :                 // Shouldn't happen
    1839           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
    1840             :                             "Step nr %d (%s) has already an input dataset",
    1841           0 :                             static_cast<int>(i), step->GetName().c_str());
    1842           2 :                 return false;
    1843             :             }
    1844         222 :             step->m_inputDataset.clear();
    1845         222 :             step->m_inputDataset.resize(1);
    1846         222 :             step->m_inputDataset[0].Set(poCurDS);
    1847             :         }
    1848             : 
    1849         377 :         if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
    1850             :         {
    1851             :             // Shouldn't happen
    1852           2 :             ReportError(CE_Failure, CPLE_AppDefined,
    1853             :                         "Step nr %d (%s) has already an output dataset",
    1854           2 :                         static_cast<int>(i), step->GetName().c_str());
    1855           2 :             return false;
    1856             :         }
    1857             : 
    1858             :         const bool bCanHandleNextStep =
    1859         599 :             i < m_steps.size() - 1 &&
    1860         224 :             step->CanHandleNextStep(m_steps[i + 1].get());
    1861             : 
    1862             :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
    1863         375 :             nullptr, GDALDestroyScaledProgress);
    1864         375 :         GDALPipelineStepRunContext stepCtxt;
    1865         380 :         if ((bCanHandleNextStep &&
    1866         750 :              m_steps[i + 1]->IsNativelyStreamingCompatible()) ||
    1867         375 :             !step->IsNativelyStreamingCompatible())
    1868             :         {
    1869         168 :             pScaledData.reset(GDALCreateScaledProgress(
    1870             :                 iCurStepWithProgress /
    1871         168 :                     static_cast<double>(countPipelinesWithProgress),
    1872         168 :                 (iCurStepWithProgress + 1) /
    1873         168 :                     static_cast<double>(countPipelinesWithProgress),
    1874             :                 pfnProgress, pProgressData));
    1875         168 :             ++iCurStepWithProgress;
    1876         168 :             stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
    1877         168 :             stepCtxt.m_pProgressData = pScaledData.get();
    1878             :         }
    1879         375 :         if (bCanHandleNextStep)
    1880             :         {
    1881           5 :             stepCtxt.m_poNextUsableStep = m_steps[i + 1].get();
    1882             :         }
    1883         390 :         if (i + 1 == m_steps.size() && m_stdout &&
    1884         390 :             step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr)
    1885             :         {
    1886           4 :             step->m_stdout = true;
    1887             :         }
    1888         375 :         if (!step->ValidateArguments() || !step->RunStep(stepCtxt))
    1889             :         {
    1890          12 :             ret = false;
    1891          12 :             break;
    1892             :         }
    1893         363 :         poCurDS = step->m_outputDataset.GetDatasetRef();
    1894         381 :         if (!poCurDS && !(i + 1 == m_steps.size() &&
    1895          12 :                           (!step->m_output.empty() ||
    1896         369 :                            step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr ||
    1897           2 :                            step->GetName() == "compare")))
    1898             :         {
    1899           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    1900             :                         "Step nr %d (%s) failed to produce an output dataset",
    1901           0 :                         static_cast<int>(i), step->GetName().c_str());
    1902           0 :             return false;
    1903             :         }
    1904             : 
    1905         363 :         if (bCanHandleNextStep)
    1906             :         {
    1907           5 :             ++i;
    1908             :         }
    1909             :     }
    1910             : 
    1911         177 :     if (pfnProgress &&
    1912         177 :         m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_STRING) == nullptr)
    1913          15 :         pfnProgress(1.0, "", pProgressData);
    1914             : 
    1915         161 :     if (!m_steps.back()->m_output.empty())
    1916             :     {
    1917             :         auto stepOutputStringArg =
    1918           6 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
    1919           6 :         if (stepOutputStringArg && stepOutputStringArg->IsOutput())
    1920             :         {
    1921           5 :             auto outputStringArg = GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
    1922           5 :             if (outputStringArg && outputStringArg->GetType() == GAAT_STRING)
    1923           5 :                 outputStringArg->Set(m_steps.back()->m_output);
    1924             :         }
    1925             :     }
    1926         155 :     else if (ret && !m_outputDataset.GetDatasetRef())
    1927             :     {
    1928         143 :         m_outputDataset.Set(poCurDS);
    1929             :     }
    1930             : 
    1931         161 :     return ret;
    1932             : }
    1933             : 
    1934             : /************************************************************************/
    1935             : /*               GDALAbstractPipelineAlgorithm::Finalize()              */
    1936             : /************************************************************************/
    1937             : 
    1938         131 : bool GDALAbstractPipelineAlgorithm::Finalize()
    1939             : {
    1940         131 :     bool ret = GDALPipelineStepAlgorithm::Finalize();
    1941         403 :     for (auto &step : m_steps)
    1942             :     {
    1943         272 :         ret = step->Finalize() && ret;
    1944             :     }
    1945         131 :     return ret;
    1946             : }
    1947             : 
    1948             : /************************************************************************/
    1949             : /*             GDALAbstractPipelineAlgorithm::GetUsageAsJSON()          */
    1950             : /************************************************************************/
    1951             : 
    1952           8 : std::string GDALAbstractPipelineAlgorithm::GetUsageAsJSON() const
    1953             : {
    1954          16 :     CPLJSONDocument oDoc;
    1955           8 :     CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
    1956             : 
    1957          16 :     CPLJSONArray jPipelineSteps;
    1958         288 :     for (const std::string &name : GetStepRegistry().GetNames())
    1959             :     {
    1960         560 :         auto alg = GetStepAlg(name);
    1961         280 :         if (!alg->IsHidden())
    1962             :         {
    1963         276 :             CPLJSONDocument oStepDoc;
    1964         276 :             CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
    1965         276 :             jPipelineSteps.Add(oStepDoc.GetRoot());
    1966             :         }
    1967             :     }
    1968           8 :     oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
    1969             : 
    1970          16 :     return oDoc.SaveAsString();
    1971             : }
    1972             : 
    1973             : //! @endcond

Generated by: LCOV version 1.14