LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 980 1034 94.8 %
Date: 2025-10-01 17:07:58 Functions: 16 16 100.0 %

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

Generated by: LCOV version 1.14