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

Generated by: LCOV version 1.14