LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 149 165 90.3 %
Date: 2025-05-15 18:21:54 Functions: 14 18 77.8 %

          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, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifndef GDALALG_ABSTRACT_PIPELINE_INCLUDED
      14             : #define GDALALG_ABSTRACT_PIPELINE_INCLUDED
      15             : 
      16             : //! @cond Doxygen_Suppress
      17             : 
      18             : #include "cpl_conv.h"
      19             : #include "cpl_json.h"
      20             : #include "gdalalgorithm.h"
      21             : #include "gdal_priv.h"
      22             : 
      23             : #include <algorithm>
      24             : 
      25             : template <class StepAlgorithm>
      26             : class GDALAbstractPipelineAlgorithm CPL_NON_FINAL : public StepAlgorithm
      27             : {
      28             :   public:
      29             :     std::vector<std::string> GetAutoComplete(std::vector<std::string> &args,
      30             :                                              bool lastWordIsComplete,
      31             :                                              bool /* showAllOptions*/) override;
      32             : 
      33             :     bool Finalize() override;
      34             : 
      35             :     std::string GetUsageAsJSON() const override;
      36             : 
      37             :     /* cppcheck-suppress functionStatic */
      38           0 :     void SetDataset(GDALDataset *)
      39             :     {
      40           0 :     }
      41             : 
      42             :   protected:
      43         170 :     GDALAbstractPipelineAlgorithm(const std::string &name,
      44             :                                   const std::string &description,
      45             :                                   const std::string &helpURL,
      46             :                                   bool standaloneStep)
      47         170 :         : StepAlgorithm(name, description, helpURL, standaloneStep)
      48             :     {
      49         170 :     }
      50             : 
      51         170 :     ~GDALAbstractPipelineAlgorithm() override
      52             :     {
      53             :         // Destroy steps in the reverse order they have been constructed,
      54             :         // as a step can create object that depends on the validity of
      55             :         // objects of previous steps, and while cleaning them it needs those
      56             :         // prior objects to be still alive.
      57             :         // Typically for "gdal vector pipeline read ... ! sql ..."
      58         390 :         for (auto it = std::rbegin(m_steps); it != std::rend(m_steps); it++)
      59             :         {
      60         220 :             it->reset();
      61             :         }
      62         340 :     }
      63             : 
      64             :     virtual GDALArgDatasetValue &GetOutputDataset() = 0;
      65             : 
      66             :     std::string m_pipeline{};
      67             : 
      68             :     std::unique_ptr<StepAlgorithm> GetStepAlg(const std::string &name) const;
      69             : 
      70             :     GDALAlgorithmRegistry m_stepRegistry{};
      71             :     std::vector<std::unique_ptr<StepAlgorithm>> m_steps{};
      72             : 
      73             :   private:
      74             :     bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
      75             : };
      76             : 
      77             : /************************************************************************/
      78             : /*              GDALAbstractPipelineAlgorithm::GetStepAlg()             */
      79             : /************************************************************************/
      80             : 
      81             : template <class StepAlgorithm>
      82             : std::unique_ptr<StepAlgorithm>
      83         507 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetStepAlg(
      84             :     const std::string &name) const
      85             : {
      86        1014 :     auto alg = m_stepRegistry.Instantiate(name);
      87             :     return std::unique_ptr<StepAlgorithm>(
      88        1014 :         cpl::down_cast<StepAlgorithm *>(alg.release()));
      89             : }
      90             : 
      91             : /************************************************************************/
      92             : /*         GDALAbstractPipelineAlgorithm::GetAutoComplete()             */
      93             : /************************************************************************/
      94             : 
      95             : template <class StepAlgorithm>
      96             : std::vector<std::string>
      97          17 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetAutoComplete(
      98             :     std::vector<std::string> &args, bool lastWordIsComplete,
      99             :     bool /* showAllOptions*/)
     100             : {
     101          17 :     std::vector<std::string> ret;
     102          17 :     if (args.size() <= 1)
     103             :     {
     104           6 :         if (args.empty() || args.front() != "read")
     105           4 :             ret.push_back("read");
     106             :     }
     107          23 :     else if (args.back() == "!" ||
     108          23 :              (args[args.size() - 2] == "!" && !GetStepAlg(args.back())))
     109             :     {
     110          70 :         for (const std::string &name : m_stepRegistry.GetNames())
     111             :         {
     112          66 :             if (name != "read")
     113             :             {
     114          62 :                 ret.push_back(name);
     115             :             }
     116             :         }
     117             :     }
     118             :     else
     119             :     {
     120          14 :         std::string lastStep = "read";
     121          14 :         std::vector<std::string> lastArgs;
     122          24 :         for (size_t i = 1; i < args.size(); ++i)
     123             :         {
     124          17 :             lastArgs.push_back(args[i]);
     125          17 :             if (i + 1 < args.size() && args[i] == "!")
     126             :             {
     127           6 :                 ++i;
     128           6 :                 lastArgs.clear();
     129           6 :                 lastStep = args[i];
     130             :             }
     131             :         }
     132             : 
     133          14 :         auto curAlg = GetStepAlg(lastStep);
     134           7 :         if (curAlg)
     135             :         {
     136           7 :             ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
     137             :                                           /* showAllOptions = */ false);
     138             :         }
     139             :     }
     140          17 :     return ret;
     141             : }
     142             : 
     143             : /************************************************************************/
     144             : /*              GDALAbstractPipelineAlgorithm::RunStep()                */
     145             : /************************************************************************/
     146             : 
     147             : template <class StepAlgorithm>
     148          95 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep(
     149             :     GDALProgressFunc pfnProgress, void *pProgressData)
     150             : {
     151          95 :     if (m_steps.empty())
     152             :     {
     153             :         // If invoked programmatically, not from the command line.
     154             : 
     155          36 :         if (m_pipeline.empty())
     156             :         {
     157           2 :             StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
     158             :                                        "'pipeline' argument not set");
     159           5 :             return false;
     160             :         }
     161             : 
     162          34 :         const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
     163          34 :         if (!this->ParseCommandLineArguments(aosTokens))
     164           3 :             return false;
     165             :     }
     166             : 
     167             :     // Handle output to GDALG file
     168          90 :     if (!m_steps.empty() && m_steps.back()->GetName() == "write")
     169             :     {
     170          90 :         if (m_steps.back()->IsGDALGOutput())
     171             :         {
     172           6 :             const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
     173           6 :             const auto &filename =
     174             :                 outputArg->GDALAlgorithmArg::template Get<GDALArgDatasetValue>()
     175           6 :                     .GetName();
     176           6 :             const char *pszType = "";
     177           6 :             if (GDALDoesFileOrDatasetExist(filename.c_str(), &pszType))
     178             :             {
     179           1 :                 const auto overwriteArg =
     180           1 :                     m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
     181           1 :                 if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
     182             :                 {
     183           1 :                     if (!overwriteArg->GDALAlgorithmArg::template Get<bool>())
     184             :                     {
     185           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     186             :                                  "%s '%s' already exists. Specify the "
     187             :                                  "--overwrite option to overwrite it.",
     188             :                                  pszType, filename.c_str());
     189           0 :                         return false;
     190             :                     }
     191             :                 }
     192             :             }
     193             : 
     194          12 :             std::string osCommandLine;
     195             : 
     196          24 :             for (const auto &path : GDALAlgorithm::m_callPath)
     197             :             {
     198          18 :                 if (!osCommandLine.empty())
     199          12 :                     osCommandLine += ' ';
     200          18 :                 osCommandLine += path;
     201             :             }
     202             : 
     203             :             // Do not include the last step
     204          18 :             for (size_t i = 0; i + 1 < m_steps.size(); ++i)
     205             :             {
     206          12 :                 const auto &step = m_steps[i];
     207          12 :                 if (!step->IsNativelyStreamingCompatible())
     208             :                 {
     209           2 :                     GDALAlgorithm::ReportError(
     210             :                         CE_Warning, CPLE_AppDefined,
     211             :                         "Step %s is not natively streaming compatible, and "
     212             :                         "may cause significant processing time at opening",
     213           2 :                         step->GDALAlgorithm::GetName().c_str());
     214             :                 }
     215             : 
     216          12 :                 if (i > 0)
     217           6 :                     osCommandLine += " !";
     218          25 :                 for (const auto &path : step->GDALAlgorithm::m_callPath)
     219             :                 {
     220          13 :                     if (!osCommandLine.empty())
     221          13 :                         osCommandLine += ' ';
     222          13 :                     osCommandLine += path;
     223             :                 }
     224             : 
     225         136 :                 for (const auto &arg : step->GetArgs())
     226             :                 {
     227         124 :                     if (arg->IsExplicitlySet())
     228             :                     {
     229          10 :                         osCommandLine += ' ';
     230          10 :                         std::string strArg;
     231          10 :                         if (!arg->Serialize(strArg))
     232             :                         {
     233           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     234             :                                      "Cannot serialize argument %s",
     235           0 :                                      arg->GetName().c_str());
     236           0 :                             return false;
     237             :                         }
     238          10 :                         osCommandLine += strArg;
     239             :                     }
     240             :                 }
     241             :             }
     242             : 
     243           6 :             return GDALAlgorithm::SaveGDALG(filename, osCommandLine);
     244             :         }
     245             : 
     246          84 :         const auto outputFormatArg =
     247          84 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
     248          84 :         const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
     249          84 :         if (outputArg && outputArg->GetType() == GAAT_DATASET &&
     250             :             outputArg->IsExplicitlySet())
     251             :         {
     252          84 :             const auto &outputFile =
     253             :                 outputArg
     254             :                     ->GDALAlgorithmArg::template Get<GDALArgDatasetValue>();
     255             :             bool isVRTOutput;
     256          84 :             if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
     257             :                 outputFormatArg->IsExplicitlySet())
     258             :             {
     259          27 :                 const auto &val =
     260             :                     outputFormatArg
     261             :                         ->GDALAlgorithmArg::template Get<std::string>();
     262          27 :                 isVRTOutput = EQUAL(val.c_str(), "vrt");
     263             :             }
     264             :             else
     265             :             {
     266          57 :                 isVRTOutput = EQUAL(
     267             :                     CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
     268             :                     "vrt");
     269             :             }
     270          97 :             if (isVRTOutput && !outputFile.GetName().empty() &&
     271          13 :                 m_steps.size() > 3)
     272             :             {
     273           1 :                 StepAlgorithm::ReportError(
     274             :                     CE_Failure, CPLE_NotSupported,
     275             :                     "VRT output is not supported when there are more than 3 "
     276             :                     "steps. Consider using the GDALG driver (files with "
     277             :                     ".gdalg.json extension)");
     278           1 :                 return false;
     279             :             }
     280          83 :             if (isVRTOutput)
     281             :             {
     282          24 :                 for (const auto &step : m_steps)
     283             :                 {
     284          24 :                     if (!step->m_outputVRTCompatible)
     285             :                     {
     286          12 :                         step->ReportError(
     287             :                             CE_Failure, CPLE_NotSupported,
     288             :                             "VRT output is not supported. Consider using the "
     289             :                             "GDALG driver instead (files with .gdalg.json "
     290             :                             "extension)");
     291          12 :                         return false;
     292             :                     }
     293             :                 }
     294             :             }
     295             :         }
     296             :     }
     297             : 
     298          71 :     if (GDALAlgorithm::m_executionForStreamOutput)
     299             :     {
     300             :         // For security reasons, to avoid that reading a .gdalg.json file writes
     301             :         // a file on the file system.
     302          44 :         for (const auto &step : m_steps)
     303             :         {
     304          46 :             if (step->GetName() == "write" &&
     305          14 :                 !EQUAL(step->m_format.c_str(), "stream"))
     306             :             {
     307           2 :                 StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
     308             :                                            "in streamed execution, --format "
     309             :                                            "stream should be used");
     310           2 :                 return false;
     311             :             }
     312             :         }
     313             :     }
     314             : 
     315          69 :     int countPipelinesWithProgress = 1;  // write
     316          93 :     for (size_t i = 1; i + 1 < m_steps.size(); ++i)
     317             :     {
     318          24 :         if (!m_steps[i]->IsNativelyStreamingCompatible())
     319           5 :             ++countPipelinesWithProgress;
     320             :     }
     321             : 
     322          69 :     GDALDataset *poCurDS = nullptr;
     323          69 :     int iCurPipelineWithProgress = 0;
     324         217 :     for (size_t i = 0; i < m_steps.size(); ++i)
     325             :     {
     326         155 :         auto &step = m_steps[i];
     327         155 :         if (i > 0)
     328             :         {
     329             :             if constexpr (std::is_same_v<decltype(step->m_inputDataset),
     330             :                                          GDALArgDatasetValue>)
     331             :             {
     332          46 :                 if (step->m_inputDataset.GetDatasetRef())
     333             :                 {
     334             :                     // Shouldn't happen
     335           0 :                     StepAlgorithm::ReportError(
     336             :                         CE_Failure, CPLE_AppDefined,
     337             :                         "Step nr %d (%s) has already an input dataset",
     338           0 :                         static_cast<int>(i), step->GetName().c_str());
     339           4 :                     return false;
     340             :                 }
     341          46 :                 step->m_inputDataset.Set(poCurDS);
     342             :             }
     343             :             else if constexpr (std::is_same_v<decltype(step->m_inputDataset),
     344             :                                               std::vector<GDALArgDatasetValue>>)
     345             :             {
     346          40 :                 if (!step->m_inputDataset.empty() &&
     347           0 :                     step->m_inputDataset[0].GetDatasetRef())
     348             :                 {
     349             :                     // Shouldn't happen
     350           0 :                     StepAlgorithm::ReportError(
     351             :                         CE_Failure, CPLE_AppDefined,
     352             :                         "Step nr %d (%s) has already an input dataset",
     353           0 :                         static_cast<int>(i), step->GetName().c_str());
     354           3 :                     return false;
     355             :                 }
     356          40 :                 step->m_inputDataset.clear();
     357          40 :                 step->m_inputDataset.resize(1);
     358          40 :                 step->m_inputDataset[0].Set(poCurDS);
     359             :             }
     360             :         }
     361         155 :         if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
     362             :         {
     363             :             // Shouldn't happen
     364           2 :             StepAlgorithm::ReportError(
     365             :                 CE_Failure, CPLE_AppDefined,
     366             :                 "Step nr %d (%s) has already an output dataset",
     367           2 :                 static_cast<int>(i), step->GetName().c_str());
     368           2 :             return false;
     369             :         }
     370             : 
     371           0 :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
     372         153 :             nullptr, GDALDestroyScaledProgress);
     373         153 :         if (i == m_steps.size() - 1 || !step->IsNativelyStreamingCompatible())
     374             :         {
     375          67 :             pScaledData.reset(GDALCreateScaledProgress(
     376             :                 iCurPipelineWithProgress /
     377             :                     static_cast<double>(countPipelinesWithProgress),
     378          67 :                 (iCurPipelineWithProgress + 1) /
     379             :                     static_cast<double>(countPipelinesWithProgress),
     380             :                 pfnProgress, pProgressData));
     381          67 :             ++iCurPipelineWithProgress;
     382             :         }
     383         153 :         if (!step->Run(pScaledData ? GDALScaledProgress : nullptr,
     384             :                        pScaledData.get()))
     385             :         {
     386           5 :             return false;
     387             :         }
     388         148 :         poCurDS = step->m_outputDataset.GetDatasetRef();
     389         148 :         if (!poCurDS)
     390             :         {
     391           0 :             StepAlgorithm::ReportError(
     392             :                 CE_Failure, CPLE_AppDefined,
     393             :                 "Step nr %d (%s) failed to produce an output dataset",
     394           0 :                 static_cast<int>(i), step->GetName().c_str());
     395           0 :             return false;
     396             :         }
     397             :     }
     398             : 
     399          62 :     if (pfnProgress)
     400           6 :         pfnProgress(1.0, "", pProgressData);
     401             : 
     402          62 :     if (!GetOutputDataset().GetDatasetRef())
     403             :     {
     404          62 :         GetOutputDataset().Set(poCurDS);
     405             :     }
     406             : 
     407          62 :     return true;
     408             : }
     409             : 
     410             : /************************************************************************/
     411             : /*               GDALAbstractPipelineAlgorithm::Finalize()              */
     412             : /************************************************************************/
     413             : 
     414             : template <class StepAlgorithm>
     415          58 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::Finalize()
     416             : {
     417          58 :     bool ret = GDALAlgorithm::Finalize();
     418         179 :     for (auto &step : m_steps)
     419             :     {
     420         121 :         ret = step->Finalize() && ret;
     421             :     }
     422          58 :     return ret;
     423             : }
     424             : 
     425             : /************************************************************************/
     426             : /*             GDALAbstractPipelineAlgorithm::GetUsageAsJSON()          */
     427             : /************************************************************************/
     428             : 
     429             : template <class StepAlgorithm>
     430           7 : std::string GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetUsageAsJSON() const
     431             : {
     432          14 :     CPLJSONDocument oDoc;
     433           7 :     CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
     434             : 
     435          14 :     CPLJSONArray jPipelineSteps;
     436         129 :     for (const std::string &name : m_stepRegistry.GetNames())
     437             :     {
     438         244 :         auto alg = GetStepAlg(name);
     439         122 :         CPLJSONDocument oStepDoc;
     440         122 :         CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
     441         122 :         jPipelineSteps.Add(oStepDoc.GetRoot());
     442             :     }
     443           7 :     oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
     444             : 
     445          14 :     return oDoc.SaveAsString();
     446             : }
     447             : 
     448             : //! @endcond
     449             : 
     450             : #endif

Generated by: LCOV version 1.14