LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1041 1146 90.8 %
Date: 2026-03-13 03:16:58 Functions: 17 17 100.0 %

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

Generated by: LCOV version 1.14