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

Generated by: LCOV version 1.14