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

Generated by: LCOV version 1.14